我大量使用std::set<int>
我经常只需要检查,如果这样一组包含许多与否。
我发现它自然地写:
if (myset.contains(number))
...
但由于缺乏contains
成员,我需要编写繁琐的:
if (myset.find(number) != myset.end())
..
或并不明显:
if (myset.count(element) > 0)
..
是否有这样的设计决策的理由?
我想这可能是因为他们试图让std::set
和std::multiset
尽可能相似。 (很显然count
有std::multiset
一个非常明智的意思。)
我个人认为这是一个错误。
如果你假装count
只是contains
的拼写错误,并写了测试,因为它看起来不那么糟糕:
if (myset.count(element))
...
它仍然是一个耻辱,但。
为了能够写if (s.contains())
,contains()
必须返回一个bool
(或类型转换为bool
,这是另一回事),像binary_search
一样。
背后的设计决定不去做这样的根本原因在于contains()
它返回一个bool
将失去有关,其中元素集合中的有价值的信息。 find()
蜜饯,并返回一个迭代器的形式的信息,因此对于像STL的通用库是更好的选择。这一直是亚历克斯·斯捷潘诺夫的指导原则,正如他常解释(例如,here)。
至于一般count()
方法,虽然它往往是一个不错的解决方法,它的问题是,它不是一个contains()
将不得不做更多的工作。
这并不是说一个bool contains()
是不是一个很好的到了,甚至必要的。前一段时间,我们有一个关于在ISO C ++标准这同一个问题long discussion - 未来提案组。
它缺乏它,因为没有人加入它。没有人加入它,因为从该std
文库并入其中设计成在界面最小的STL的容器。 (请注意,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
必须找到他们每个人并计数。
含有的7 1十亿份多集(你知道,如果你用完)可以有一个非常缓慢的.count(7)
,但可以有一个非常快的contains(7)
。
通过上述扩展方法,我们可以通过使用lower_bound
,比较end
,然后比较的元素使它成为这种情况下更快。这样做,对于一个无序喵以及有序喵需要花哨SFINAE或特定容器重载不过。
您正在调查具体情况,并没有看到更大的画面。正如documentation std::set
规定符合AssociativeContainer概念的要求。对于这一概念也没有任何意义有contains
方法,因为它是std::multiset
和std::multimap
几乎无用,但count
工作正常,所有的人。虽然方法contains
可以添加为count
为std::set
,std::map
及其散列版本(如length
在size()
std::string
)的别名,但看起来像库创建者没有看到它真正的需要。
虽然我不知道为什么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)) ...
真正的原因是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();
}
当然,这一切仅仅是一种猜测。
什么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;
另一个原因是,它会给一个程序员的假象,标准::集是在数学集理论意义上的集中。如果他们实现这一点,那么其他许多问题将遵循:如果一个的std ::集包含有()的价值,为什么没有把它的另一套?哪里联盟(),路口()等一系列操作和谓词?
答案是,当然,有些设定操作都必须在(标准:: set_union()等)的功能已经实现,其他作为平凡实现为包括()。函数和函数对象的数学抽象比对象成员更好地工作,并且它们并不限于特定的容器类型。
如果一个需要实现一个完整的数学设定的功能,他不仅有底层容器的选择,也是他的实现细节进行选择,例如,将与不可变对象他theory_union()函数的工作,更适合于函数式编程,或将它修改其操作数和节省内存?难道被实现为函数对象从一开始,或者它会是更好地执行是一个C函数,并在需要时使用std ::函数<>?
因为它是现在,性病::设置仅仅是一个容器,非常适合于在数学意义上一套执行,但它几乎是远从是一个理论上的载体是一个理论的一组为标准::向量。