为什么std :: set没有“包含”成员函数?

时间:2017-03-01 13:04:17

标签: c++ stl stdset

我正在大量使用std::set<int>而且我经常需要检查这样的集合是否包含数字。

我觉得写作很自然:

if (myset.contains(number))
   ...

但由于缺少contains成员,我需要编写繁琐的内容:

if (myset.find(number) != myset.end())
  ..

或者不那么明显:

if (myset.count(element) > 0) 
  ..

这个设计决定是否有理由?

10 个答案:

答案 0 :(得分:144)

我认为可能是因为他们试图让std::setstd::multiset尽可能相似。 (显然countstd::multiset具有完全明智的含义。)

我个人认为这是一个错误。

如果假装count只是contains的拼写错误并将测试编写为:

,那么看起来并不是那么糟糕
if (myset.count(element)) 
   ...
但是,它仍然是一种耻辱。

答案 1 :(得分:38)

为了能够写if (s.contains())contains()必须返回bool(或可转换为bool的类型,这是另一个故事),如{{1}确实。

设计决策背后的根本原因这样做是binary_search返回contains()丢失关于元素在集合中的位置的有价值信息bool以迭代器的形式保留并返回该信息,因此对于像STL这样的通用库来说是更好的选择。这一直是Alex Stepanov的指导原则,正如他经常解释的那样(例如,here)。

对于find()方法一般来说,虽然它通常是一个好的解决方法,但它的问题是它比 count() 更多的工作会必须做

这并不是说contains()不是一个非常好的甚至是必要的。不久之前,我们有一个long discussion关于这个同样的问题 ISO C ++标准 - 未来提案组。

答案 2 :(得分:22)

缺乏它,因为没有人添加它。没有人添加它,因为来自STL的容器std库在界面中被设计为最小的。 (请注意,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类型编写扩展方法。

这样做更有意义:

if (some_set.count(some_element)) {
}

但是我对扩展方法方法很有兴趣。

真正令人遗憾的是,在containsmultimap上写一个有效的multiset可能会更快,因为他们只需找到一个元素,而count有找到每个并计算它们

一个包含10亿份7的多重集(你知道,如果你用完了)可能会非常慢.count(7),但可能会非常快contains(7)

使用上述扩展方法,我们可以通过使用lower_bound,与end进行比较,然后与元素进行比较,使此更快。对于无序的喵喵和有序的喵喵来说,这需要花哨的SFINAE或容器特定的重载。

答案 3 :(得分:12)

您正在调查特定情况,而不是看到更大的图片。正如documentation std::set中所述符合AssociativeContainer概念的要求。对于这个概念,使用contains方法没有任何意义,因为它对std::multisetstd::multimap几乎没用,但count适用于所有这些方法。虽然可以将方法contains添加为countstd::set的{​​{1}}的别名及其散列版本(例如std::map中的length { {1}}),但看起来像图书馆创建者并没有真正需要它。

答案 4 :(得分:10)

虽然我不知道为什么std::set没有containscount只返回01, 你可以编写一个模板化的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)) ...

答案 5 :(得分:7)

set的真正原因对我来说是一个谜,但map中对同一设计的一种可能解释可能是阻止人们意外编写低效代码:

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中的某个项目只在set中出现,那么这种设计可能会阻止我们编写代码:

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();
}

当然这一切只是猜测。

答案 6 :(得分:4)

从c ++ 20开始,

bool contains( const Key& key ) const

可用。

答案 7 :(得分:0)

binary_search怎么样?

 set <int> set1;
 set1.insert(10);
 set1.insert(40);
 set1.insert(30);
 if(std::binary_search(set1.begin(),set1.end(),30))
     bool found=true;

答案 8 :(得分:0)

contains()必须返回布尔值。使用C ++ 20编译器,我得到以下代码输出:

@Library(['librarty1', 'librarty2', 'librarty3']) _

答案 9 :(得分:-1)

另一个原因是它会给程序员一个假象,即std :: set是数学集理论意义上的集合。如果他们实现了这个,那么接下来会出现许多其他问题:如果std :: set的值为contains(),为什么不将它用于另一个集合? union(),intersection()和其他set操作和谓词在哪里?

当然,答案是,某些set操作已经作为(std :: set_union()等)中的函数实现,而其他操作则通过contains()实现。数学抽象的函数和函数对象比对象成员更好地工作,并且它们不限于特定的容器类型。

如果需要实现完整的数学集功能,他不仅可以选择底层容器,还可以选择实现细节,例如,他的theory_union()函数是否可以使用不可变对象,更适合用于函数式编程,还是会修改其操作数并节省内存?它是从一开始就作为函数对象实现的,或者最好是实现C函数,并使用std :: function&lt;&gt;如果需要?

就像现在一样,std :: set只是一个容器,非常适合数学意义上的set的实现,但它几乎远不是作为理论向量的std :: vector的理论集合。