为什么不在 C++ 容器中实现 contains 函数?

Why not implement contains function in c++ Containers?

我最初的问题是:为什么在 C++ 中 contains() 函数在 Containers 中缺失?

所以我寻找了一个解释,我发现了一些有趣的东西,为什么一些其他功能没有在所有 Containers 中实现(主要是因为性能问题和便利性)。

我知道您可以使用 algorithm 库中的 find 函数,或者您可以使用 Iterator 编写自己的函数,但我无法理解的是 为什么在set中,例如,实现了contains函数(它被称为find),而在vectorqueue中没有实现.

我也很清楚为什么 Container 类 不像 Collections 在 Java 中那样共享一个公共接口(感谢 this 的回答)但是在这个如果我找不到为什么不在所有容器 类 中实现 contains() 函数的原因(或者至少在 vector 之类的容器中)。

谢谢

std::set 实现自己的 find 的原因是 "generic" std::find 进行线性搜索,而 std::set 可以做得更好而不是通过在 O(log2 n ).

中搜索其树表示 另一方面,

std::vectorstd::list 不能比线性时间更快地搜索,因此它们依赖于 std::find 实现。

请注意,您仍然可以将 std::find 应用到 std::set 以进行线性搜索;它只是不如使用 set 自己的 find 成员函数那么有效。

std::set<int> s {1, 2, 3, 4, 5, 6};
auto res = std::find(s.begin(), s.end(), 3);
std::cout << *res << std::endl; // prints 3

因为这是一个糟糕的设计模式。如果您在线性容器上重复使用 "find",则说明您的代码存在问题。平均和最坏情况下的时间复杂度仍然是 O(n),这意味着您做出了错误的选择。

例如,std::mapstd::unordered_map 具有 find 允许 O(logn) 和 O(1) 查找的成员函数。这是因为容器通过这些方法使用高效的项目查找:这就是应该使用容器的方式。

如果您权衡了所有选项,并确定线性容器是您数据的最佳模型,但确实需要在极少数情况下查找元素,std::find() 可以让您做到这一点。你不应该依赖它。我将其视为 Python 和 Java 中的反模式,以及 C++ 中的优势。

作为个人笔记,3年前,当我还是一个初学者时,我写了很多if mydict in list: do_something()。我认为这是个好主意,因为 Python 使列表项成员资格成为惯用的。我不知道更好。这导致我编写了糟糕的代码,直到我了解到为什么线性搜索与二进制搜索和哈希图查找相比效率如此低下。编程语言或框架应该支持好的设计模式,并阻止坏的设计模式。启用线性搜索是一种糟糕的设计模式。

出于某种原因,其他答案侧重于广义和 per-container find 方法的复杂性。但是他们无法解释 OP 的问题。缺少有用的实用方法的真正原因是来自不同文件的 类 在 C++ 中的使用方式。如果每个不包含任何特殊属性的容器都有 contains 方法执行线性复杂度的通用搜索,我们将遇到每个容器 header 也包含 <algorithm> 或每个容器 header 重新实现了它自己的 find 算法(这更糟)。在预处理器包含(即 copy-pastes)每个包含的 header 之后,每个翻译单元编译期间构建的相当大的文档会变得更大。编译将花费更多时间。并且相当隐秘的编译错误消息可能会变得更长。当 non-member 函数可以完成工作(并且只包含您将要使用的东西)时,不创建成员函数的旧原则存在是有原因的。

请注意,最近有一项针对 uniform call syntax 的提案可能允许将 mix-in 实用方法转换为 类。如果它上线,可能会像这样编写扩展函数:

template< typename TContainer, typename TItem > bool
contains(TContainer const & container, TItem const & item)
{
     // Some implementation possibly calling container member find
     // or ::std::find if container has none...
}

::std::vector< int > registry;
registry.contains(3); // false

容器不共享 "common (inherited) interface"(如 Java)是有充分理由的,这也是 C++ 泛型如此强大的原因。既然可以为所有容器编写一次代码,为什么还要为每个容器编写代码呢?这是 STL 建立的主要原则之一。

如果使用依赖于公共 inherited 接口的成员函数的容器,则必须为每个容器编写一个 find 函数。那是浪费和糟糕的工程。好的设计表明,如果你只能在一个地方编写代码,你应该这样做,因为你只需要从那个地方删除错误,并且在一个地方修复错误可以修复所有地方的错误

所以 STL 背后的理念是 算法 容器 [=] 中分离出来 40=] 这样你只需编写一次 算法 并且它适用于 所有 容器。一旦 algorithm 被调试,它被调试为 all 容器。

美中不足的是,一些容器由于其内部结构可以做出更有效的决策。对于这些容器,添加了一个 type specific 函数,它将利用这种效率。

但是大多数函数应该容器分开。它被称为 解耦 ,它在促进代码重用的同时减少了错误,通常比 多态性 更重要,后者是库喜欢 Java 容器使用(一个通用的继承接口)。