我正在大量使用std::set<int>
而且我经常需要检查这样的集合是否包含数字。
我觉得写作很自然:
if (myset.contains(number))
...
但由于缺少contains
成员,我需要编写繁琐的内容:
if (myset.find(number) != myset.end())
..
或者不那么明显:
if (myset.count(element) > 0)
..
这个设计决定是否有理由?
答案 0 :(得分:144)
我认为可能是因为他们试图让std::set
和std::multiset
尽可能相似。 (显然count
对std::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)) {
}
但是我对扩展方法方法很有兴趣。
真正令人遗憾的是,在contains
或multimap
上写一个有效的multiset
可能会更快,因为他们只需找到一个元素,而count
有找到每个并计算它们。
一个包含10亿份7的多重集(你知道,如果你用完了)可能会非常慢.count(7)
,但可能会非常快contains(7)
。
使用上述扩展方法,我们可以通过使用lower_bound
,与end
进行比较,然后与元素进行比较,使此更快。对于无序的喵喵和有序的喵喵来说,这需要花哨的SFINAE或容器特定的重载。
答案 3 :(得分:12)
您正在调查特定情况,而不是看到更大的图片。正如documentation std::set
中所述符合AssociativeContainer概念的要求。对于这个概念,使用contains
方法没有任何意义,因为它对std::multiset
和std::multimap
几乎没用,但count
适用于所有这些方法。虽然可以将方法contains
添加为count
,std::set
的{{1}}的别名及其散列版本(例如std::map
中的length
{ {1}}),但看起来像图书馆创建者并没有真正需要它。
答案 4 :(得分:10)
虽然我不知道为什么std::set
没有contains
但count
只返回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();
}
并像这样使用它:
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
次查询。
当我们意识到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();
}
当然这一切只是猜测。
答案 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的理论集合。