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

Jab*_*cky 96 c++ stl stdset

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

我觉得写作很自然:

if (myset.contains(number))
   ...
Run Code Online (Sandbox Code Playgroud)

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

if (myset.find(number) != myset.end())
  ..
Run Code Online (Sandbox Code Playgroud)

或者不那么明显:

if (myset.count(element) > 0) 
  ..
Run Code Online (Sandbox Code Playgroud)

这个设计决定有理由吗?

Mar*_*ica 146

我想这可能是因为他们试图制作std::setstd::multiset尽可能相似.(显然count有一个非常明智的意义std::multiset.)

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

如果你假装count只是拼写错误contains并将测试编写为:它看起来并不那么糟糕:

if (myset.count(element)) 
   ...
Run Code Online (Sandbox Code Playgroud)

尽管如此,这仍然是一种耻辱.

  • 或者,他们可能已经看到不需要额外的成员`contains()`,理由是它是多余的,因为对于任何`std :: set <T> s`和`T t`,s的结果.contains(t)`与`static_cast <bool>(s.count(t))`的结果完全相同.当使用条件表达式中的值将隐式地将其强制转换为`bool`时,他们可能会觉得`count()`足以达到目的. (8认同)
  • 顺便说一下,它与map和multimaps完全相同(这与丑陋相同,但与使用`.end()`的所有这些比较相比不那么难看. (5认同)
  • @MartinBonner如果把它遗弃的理由是愚蠢的,这并不重要.如果谈话不是100%的最终理由,那也没关系.你在这里的答案只是对你认为*应该如何的合理猜测.谈话,以及某人的回答不仅涉及它,而且任务提出它(即使他们没有)无论是如何看待它,都无可争议地接近真相而不是这个猜测.至少你应该至少*在这个答案中提到它,这将是一个很大的改进,并且将是负责任的事情. (3认同)
  • 也就是说,在某些情况下,有一个以“bool”形式返回结果的函数会很有用,并且名称“contains”比名称“count”稍微清晰一些,这将允许人们更快地解析代码不熟悉“set”。 (2认同)
  • 拼错?`if(myset.ICanHaz(element))...`:D (2认同)
  • 正确的。我现在已经通读了整个线程,a) 这是 2014 年底和 2015 年初的结果(不是最初的标准化工作);b) 它似乎完全专注于“contains”作为“find != end”的同义词,而不是“count != 0”,并且如果你提供“contains”,人们就会执行“if (cont.contains( val)) { use( cont[val]); }` 涉及整个*两次*查找,因此效率较低。后一个论点不适用于集合,实际上我不在乎 - 我很乐意用性能的两倍来换取更具可读性的代码。 (2认同)
  • @JasonC:你能继续编辑一下底部的部分吗?我真的不明白你想要提出的观点,而评论可能不是澄清这一点的最好方法.谢谢! (2认同)
  • @JustinTime 尽管从概念上将 `count` 转换为 boolean 等效于 `contains`,但 `count` 无法在找到第一个匹配项后立即返回,因为 `contains` 可以,它必须继续搜索整个集合。我怀疑设计者希望关心这个性能问题的程序员使用 `.find() != .end()` 而不是 `.count()`;太糟糕了`.end()` 不是假的。 (2认同)

Leo*_*aar 41

为了能够写if (s.contains()),contains()必须返回一个bool(或一个类型可转换为bool,这是另一个故事),就像binary_search.

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

至于count()方法,虽然它通常是一个好的解决方法,但它的问题在于它做的工作比 contains() 不得不做的更多.

这并不是说a bool contains()不是一个非常好的甚至是必要的.不久前,我们在ISO C++标准 - 未来提案组中对此同样的问题进行了长时间的讨论.

  • 值得注意的是,该讨论以最接近达成共识的方式结束,并且**被要求为其撰写提案. (4认同)
  • "...*contains()返回一个bool会丢失关于元素在集合中的位置的有价值的信息*" - 如果用户调用`myset.contains()`(如果它存在),那将是一个漂亮的强烈表明该信息没有价值(在该情况下对用户而言). (4认同)

Yak*_*ont 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)};
}
Run Code Online (Sandbox Code Playgroud)

使用:

if (some_set->*contains(some_element)) {
}
Run Code Online (Sandbox Code Playgroud)

基本上,您可以std使用此技术为大多数C++ 类型编写扩展方法.

这样做更有意义:

if (some_set.count(some_element)) {
}
Run Code Online (Sandbox Code Playgroud)

但我对扩展方法方法很有兴趣.

真正令人伤心的是,写一个有效的contains可能会更快multimap或者multiset,因为他们只需要找到一个元素,而count必须找到它们并计算它们.

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

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

  • @nwp`std :: multiset :: count`可以 (5认同)
  • 我似乎错过了任何"喵"应该引用的笑话. (3认同)
  • 10亿份7?在这里我认为`std :: set`不能包含重复项,因此`std :: set :: count`将始终返回"0"或"1". (2认同)
  • @nwp我在“ set”一词周围缺少“反引号”是因为我没有专门指称“ std :: set”。为了让您感觉更好,我将添加多个 (2认同)
  • @ user2357112 meow是“集合或地图”的占位符。询问[STL](https://nuwen.net/stl.html)为什么。 (2认同)

Sla*_*ica 12

你正在研究特定的情况,而不是看到更大的图片.如文档中所述 std::set满足AssociativeContainer概念的要求.对于这一概念它没有任何意义,有contains方法,因为它是几乎无用的std::multisetstd::multimap,但count对所有的人都工作正常.虽然方法contains可以被添加为一个别名countstd::set,std::map并且其散列版本(如length用于size()std::string),但看起来像库创建者没有看到它真正的需要.

  • 询问多集,多图或地图是否包含某些内容对我来说非常有意义.事实上,`contains`在set/map上的努力是相同的,但在multiset/multimap上可以比`count`更快*. (24认同)
  • 请注意,`string`是一个怪物:它存在于STL之前,它具有`length`和所有那些基于索引的方法,然后被"容器化"以适应STL模型...而不删除现有方法出于向后兼容的原因.参见[GotW#84:Monoliths Unstrung](http://www.gotw.ca/gotw/084.htm)=>`string`确实违反了"最低成员函数"设计原则. (8认同)
  • @Slava这就像说'size()`和`empty()`是重复的,但很多容器都有. (6认同)
  • 但后来问题变成了"为什么值得拥有像这样的AssociativeContainer概念?" - 我不确定事后会是什么. (5认同)
  • AssociativeContainer不要求类*不*具有`contains`方法. (5认同)
  • @MartinBonner所以你可以编写一个接受`AssociativeContainer`的泛型函数,而不仅仅是`std :: set`.当概念被添加到语言中时(我希望至少)会更有意义. (3认同)

rus*_*tyx 10

虽然我不知道为什么std::set没有,containscount只返回0或者1,你可以写一个模板化的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();
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

    if (contains(myset, element)) ...
Run Code Online (Sandbox Code Playgroud)

  • @bolov:你的观点?`std.:: string`不是STL的一部分!它是标准库的一部分,具有追溯性的模板... (6认同)
  • "STL努力提供最小的界面"*chough*`std :: string`*cough* (4认同)
  • -1,因为这实际上是`contains`方法确实存在的事实直接矛盾,它只是以一种愚蠢的方式命名. (3认同)
  • @MatteoItalia`count`可以更慢,因为如果代码与`multiset`共享,它实际上需要做两个`find`s来获取范围的开始和结束. (3认同)
  • OP已经知道它是多余的,但显然希望代码明确地读取`contains`.我认为没有错.@MarkRansom小SFINAE是为了防止这个模板绑定到它不应该的东西. (2认同)

Mar*_*dik 7

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

if (myMap.contains("Meaning of universe"))
{
    myMap["Meaning of universe"] = 42;
}
Run Code Online (Sandbox Code Playgroud)

这将导致两次map查找.

相反,你被迫获得一个迭代器.这给了你一个精神暗示你应该重用迭代器:

auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
    position->second = 42;
}
Run Code Online (Sandbox Code Playgroud)

它只消耗一次map查找.

当我们意识到set并且map是由相同的肉体制成时,我们也可以应用这个原则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();
}
Run Code Online (Sandbox Code Playgroud)

当然这一切只是猜测.

  • 除了人们可以写`if(myMap.count("宇宙的意义"))`就好了,所以......? (7认同)
  • 这不可能是对的.他们可以使用`contains`和`count`轻松编写效率低下的代码. (2认同)

小智 6

从c++20开始,

bool contains( const Key& key ) const

可用。