为什么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::setstd::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 ++ stdtypes编写扩展方法。

这样做更有意义:

 if (some_set.count(some_element)) { } 

但是我很喜欢这个扩展方法。

真正可悲的是,编写一个高效的containsmultimapmultiset contains可能会更快,因为他们只需要find一个元素,而count必须find每个元素并对它们进行计数

包含10亿份7(你知道,万一你用完了)的multiset可以有一个非常慢的.count(7) ,但可以有一个非常快的contains(7)

使用上面的扩展方法,我们可以通过使用lower_bound ,比较end ,然后与元素进行比较来使其更快。 做一个无序的喵喵,以及一个有序的喵,将需要花哨的SFINAE或容器特定的重载,但是。

你正在寻找特殊的情况,而不是看到更大的图片。 正如文档中所述, std::set符合AssociativeContainer概念的要求。 对于这个概念, contains方法没有任何意义,因为它对于std::multisetstd::multimap来说几乎没有用处,但是count对所有这些方法都可以正常工作。 尽pipe方法contains可以作为std::setstd::map和它们的散列版本(比如std::string size() length size()一个别名被添加,但是看起来像库创build者没有看到真正的需要它。

虽然我不知道为什么std::set没有contains但只有返回01 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查找。

当我们意识到setmap是由同一个肉体制成的,我们也可以运用这个原理来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差不多。