为什么std :: set没有“contains”成员函数?
我大量使用std::set<int>
,我经常只需要检查这样的一个集合是否包含一个数字。
我很自然地写:
if (myset.contains(number)) ...
但是由于缺less一个contains
成员,我需要编写繁琐的代码:
if (myset.find(number) != myset.end()) ..
或者不那么明显:
if (myset.count(element) > 0) ..
有这个devise决定的原因吗?
我想这可能是因为他们试图使std::set
和std::multiset
尽可能相似。 (显然, count
对于std::multiset
有一个非常明智的含义。)
我个人认为这是一个错误。
如果你假装count
只是contains
一个拼写错误,并且把testing写为:
if (myset.count(element)) ...
这仍然是一个耻辱。
为了能够编写if (s.contains())
, contains()
必须返回一个bool
(或者一个可转换为bool
的types,这是另一个故事),就像binary_search
一样。
devise决定不这样做的根本原因是contains()
返回一个bool
会丢失有关元素在集合中的位置的有价值的信息 。 find()
保存并以迭代器的forms返回这些信息,因此对于像STL这样的通用库来说是一个更好的select。 这一直是Alex Stepanov的指导原则,正如他经常解释的(例如, 这里 )。
至于一般的count()
方法,虽然它通常是一个好的解决方法,但是它的问题在于它比 contains()
要做的 工作 要多 。
这并不是说bool contains()
不是一个非常好的,甚至是不必要的。 前段时间,我们在ISO C ++标准 – 未来build议组中讨论了这个同样的问题。
它缺乏它,因为没有人添加它。 没有人添加它,因为从STL的容器, std
库合并在哪里devise是最小的界面。 (请注意std::string
不是以相同的方式来自STL)。
如果你不介意一些奇怪的语法,你可以伪造它:
template<class K> struct contains_t { K&& k; template<class C> friend bool operator->*( C&& c, contains_t&& ) { auto range = std::forward<C>(c).equal_range(std::forward<K>(k)); return range.first != range.second; // faster than: // return std::forward<C>(c).count( std::forward<K>(k) ) != 0; // for multi-meows with lots of duplicates } }; template<class K> containts_t<K> contains( K&& k ) { return {std::forward<K>(k)}; }
使用:
if (some_set->*contains(some_element)) { }
基本上,你可以使用这种技术为大多数C ++ std
types编写扩展方法。
这样做更有意义:
if (some_set.count(some_element)) { }
但是我很喜欢这个扩展方法。
真正可悲的是,编写一个高效的contains
在multimap
或multiset
contains
可能会更快,因为他们只需要find一个元素,而count
必须find每个元素并对它们进行计数 。
包含10亿份7(你知道,万一你用完了)的multiset可以有一个非常慢的.count(7)
,但可以有一个非常快的contains(7)
。
使用上面的扩展方法,我们可以通过使用lower_bound
,比较end
,然后与元素进行比较来使其更快。 做一个无序的喵喵,以及一个有序的喵,将需要花哨的SFINAE或容器特定的重载,但是。
你正在寻找特殊的情况,而不是看到更大的图片。 正如文档中所述, std::set
符合AssociativeContainer概念的要求。 对于这个概念, contains
方法没有任何意义,因为它对于std::multiset
和std::multimap
来说几乎没有用处,但是count
对所有这些方法都可以正常工作。 尽pipe方法contains
可以作为std::set
, std::map
和它们的散列版本(比如std::string
size()
length
size()
一个别名被添加,但是看起来像库创build者没有看到真正的需要它。
虽然我不知道为什么std::set
没有contains
但只有返回0
或1
count
,你可以写一个模板化的contains
帮助函数,如下所示:
template<class Container, class T> auto contains(const Container& v, const T& x) -> decltype(v.find(x) != v.end()) { return v.find(x) != v.end(); }
像这样使用它:
if (contains(myset, element)) ...
set
的真正原因对我来说是一个谜,但是在map
这个相同的devise的一个可能的解释可能是防止人们偶然编写低效的代码:
if (myMap.contains("Meaning of universe")) { myMap["Meaning of universe"] = 42; }
这将导致两个map
查找。
相反,你被迫获得一个迭代器。 这给你一个心理暗示,你应该重用迭代器:
auto position = myMap.find("Meaning of universe"); if (position != myMap.cend()) { position->second = 42; }
它只消耗一个map
查找。
当我们意识到set
和map
是由同一个肉体制成的,我们也可以运用这个原理来set
。 也就是说,如果我们只想在set
中存在一个项目,那么这个devise可以防止我们编写这样的代码:
struct Dog { std::string name; void bark(); } operator <(Dog left, Dog right) { return left.name < right.name; } std::set<Dog> dogs; ... if (dogs.contain("Husky")) { dogs.find("Husky")->bark(); }
当然,这只是一个猜测。
另一个原因是它会给程序员一个错误的印象,std :: set是一个math集合理论意义上的集合。 如果他们实现了这一点,那么很多其他的问题将会出现:如果一个std :: set包含了一个值(),为什么没有另一个呢? union(),intersection()和其他set操作和谓词在哪里?
答案当然是,一些set操作已经作为(std :: set_union()等)中的函数实现了,其他的都像contains()一样被实现。 函数和函数对象在math抽象上比对象成员更好地工作,它们不限于特定的容器types。
如果需要实现一个完整的math集function,他不仅可以select底层容器,还可以select实现细节,例如他的theory_union()函数是否可以与不可变对象一起工作,更适合函数式编程,还是会修改它的操作数并节省内存? 它会从一开始就作为函数对象来实现,还是更好地实现一个C函数,如果需要的话使用std :: function <>?
就像现在一样,std :: set只是一个容器,非常适合math意义上的集合的实现,但它与std :: vector的理论集合离理论vector差不多。