C++:STL和Boost的替代方案?

22

C++是一种多范式的语言,而STLBoost则是面向语言的函数范式。STL由容器(用于存储数据)、迭代器(用于访问数据)和算法(用于操作数据的函数)组成。算法函数通过使用迭代器应用于容器。作为一个副作用,这些方法不是容器类的一部分,而是完全分离的。(这避免了库编写者的冗余,但对库用户来说则很痛苦。)

是否存在C++的STL/Boost替代品,以更传统的面向对象方式提供此类容器?我正在寻找字符串、向量、链表、映射、树、哈希表等容器。容器应易于继承和扩展。相比之下,从STL/Boost扩展类是非常糟糕的想法,而且这是设计者所刻意的。

PS:请勿在下面的回复空间中宣扬STL/Boost的优点。我已经对它们非常了解! :-)


8
我认为你可能错误地使用了“命令式”和“函数式”编程这两个术语。 - beduin
2
Chris Jester-Young:请回复而不是评论,这样大家就可以对您的答案进行投票并通过描述来扩展它。 - Ashwin Nanjappa
3
你曾经尝试过扩展STL容器吗?你认为“正确使用它们实在是一项充满陷阱的任务”,并且认为Java/C#更加易用和优秀。如果你能详细解释一下你的顾虑,或许有人可以帮助你解决问题。(例如,如果你的问题是关于转发构造函数和非虚析构函数的,那么我认为这不是一个很大的问题,因为模板构造函数可以很好地进行转发,并且你提到的“扩展”甚至没有增加数据成员。) - Tony Delroy
8
@Ashwin:一点也不,从它们继承是一个坏主意,使用组合来扩展它们是非常自然的。 - Matthieu M.
4
注意,通过继承来扩展容器通常不能满足里氏替换原则。因此你在寻找一个做糟糕面向对象编程(或者说类导向编程)的库。 - ltjax
显示剩余4条评论
8个回答

32
许多(大多数!)旧的C++库使用的容器与Java和C#中使用的容器非常相似。
这些库的一些例子包括COOLET++NIH Class Library和Rogue Wave Tools.h++(以前有一个指向Tools.h++的链接,但现在显然已经过时了,链接已经失效,至少在我快速浏览Rogue Wave网站时,我找不到当前的链接)。
两点:
最多,这些只是一种灵感的来源。我非常确定至少有10年(通常更多是20年)没有更新过它们中的任何一个。几乎没有任何机会它们能够与任何合理现代的编译器编译通过。
我想要明确地指出,我提供这些链接只是为了回答一个非常具体的问题。我绝对不建议您使用以上任何代码,也不建议您将它们作为灵感来源。
为了确保我表达清楚,至少在我看来:
你问题中的指控完全是错误的。
你试图做的事情完全是疯狂的!
你在浪费时间。
以这种方式编写代码是一个非常糟糕的主意。坚决拒绝!
如果你坚持这样做,你将成为一个被遗弃的人。
即使是不太理解原因的非程序员,也会开始强烈讨厌你。
你的狗会把你的鞋子和床当作厕所。
你自己负责。你已经被警告了!
闭路字幕适用于幽默感不足的人:当然,其中一些是为了幽默而写的——尽管这个主意真的非常糟糕。

26

这样可以避免库编写者的冗余,但对库使用者来说很痛苦。

我完全不同意这个前提。即使我同意,它也是一个过度概括化的陈述,并不适用于每个库用户。但无论如何,这是一个主观的陈述,所以我会忽略它。


有没有C++的替代STL/Boost的容器,以更传统的面向对象方式提供这些容器?

...

容器应该有直接操作它们的方法。(例如,调用vector.sort()而不是sort(vector.begin(), vector.end()))

当然可以。只需创建自己的容器,将标准容器作为数据成员并通过成员函数根据需要委托调用它们和算法。这相当简单易行:

template<typename T>
class MyVector
{
public:
    void sort()
    {
        std::sort(vec.begin(), vec.end());
    }

    // ...
private:
    std::vector<T> vec;
};

在C++中没有任何东西阻止您做这样的事情,具有多范式的C++正是让您不同意的原因。

如果您不想编写包装器函数,您可能可以使用私有继承和使用声明。


STL/Boost使得从它们的容器派生和扩展变得棘手。

那是因为你不应该从它们那里派生。正确的方法是使用组合,就像我上面介绍的代码片段一样。


2
在计算机模拟中:使用组合方式,你最终会不得不重新编写所有所需方法的包装器。我曾经这样做过,非常痛苦。 - Ashwin Nanjappa
11
STL的整个目标是容器易于扩展。只需添加另一个非成员、非友元函数即可完成操作。如果您想要在您提出的实现中扩展一个容器,那会很麻烦。请注意保持原意,同时让翻译更加通俗易懂。 - Xeo

7

你正在走错方向。如果你想编写Java程序,请使用Java编程。如果你使用C++编程,就像C++程序员一样编程。永远顺应潮流,不要逆流而行。


3
很抱歉,如果这不是你感兴趣的答案,但是C++并不是一个“面向对象语言”。试图在C++中成为“面向对象纯粹主义者”是完全错误的。请放心,我的翻译不会改变原意,而且会使内容更加通俗易懂。 - wilhelmtell
1
@Ashwin:我猜我不是一个“伟大的黑客”,因为我在(非平凡的)C++程序中使用了多种范例,因为有些问题使用不同的策略更容易解决。毕竟,每个“伟大的黑客”只使用了一种。不要介意我的35个不错的回答、24个启发式的回答、2个好的回答徽章和800多个c++标签的赞 - In silico
4
@Ashwin:“但是,当你想要与团队一起构建一个大型项目时,你需要强制使用仅有的C++子集。” - 再次说,我不同意这个前提。为什么需要强制使用仅有的C++子集?特别是如果这个子集恰好是解决问题的最清洁方式? - In silico
2
@Ashwin:我从未说过你这样做了。但请考虑一下其他可能不同意你提供的博客文章的人是如何做事情的,除了那些你同意的人之外。我们不是白痴;如果“STL方式”真的很糟糕,我们就不会使用它! - In silico
4
为什么你一直坚持认为Qt的方法不同于STL?这是不正确的! - Emile Cormier
显示剩余6条评论

7

STL和Boost是尽可能面向对象的。

  1. 就理论而言,成员函数和以第一个参数重载的自由函数是相同的东西。在实践中它们表现非常相似,包括继承,因此,在C++中,你应该认真考虑将以(可能是const)引用作为第一个参数的自由函数作为其第一个参数的方法。

    自由函数的优点在于它们可以针对现有类定义,允许您添加接口到现有类。这就是为什么STL和特别是boost如此经常使用它们的原因。成员函数的主要优点是它们可以是虚拟的(但虚拟方法应该是私有的!)

  2. 你不想通过派生来扩展集合。通常情况下,除非它是专门设计用于派生的抽象基类,否则你不想通过派生来扩展任何东西。请参见有关组合优于继承的优势的问题


自由函数的一个限制是,ADL 要求函数在其参数的命名空间中定义。将函数添加到 std 命名空间是未定义行为。/哭 - Matthieu M.
1
但是您可以为用户定义的类型专门化std名称空间成员。 - sehe

5

看看Qt的方法,我一直是它的粉丝。

更新了链接。


OneOfOne:谢谢。Qt实际上是我所知道的唯一遵循这种方法的库!我希望有人能指出其他的选择 :-) - Ashwin Nanjappa
16
什么?Qt容器几乎与STL容器相同。没有虚函数,没有包含排序语句等等。它们有什么不同之处? - edA-qa mort-ora-y
链接损坏,请修复。 - tuket
我也喜欢Qt容器,它们很方便。它们有一些有用的方法,比如QList :: contains,QList :: operator <<,QStringList :: join(),QString :: split... 我希望它成为标准。 此外,没有命名空间可用于编写更短的代码。std命名空间已经变得太大了,它的成员可能会与您的代码发生冲突,因此只有少数项目包含“using namespace std”。 - Kiruahxh

0

我来晚了,而且我知道OP特别要求不要“卖弄”,因为他们已经知道STL了,但是...

有一篇Dr. Dobbs采访亚历克斯·斯蒂潘诺夫(Alex Stepanov)的旧文章,他是泛型编程的先驱和STL的主要贡献者。它在多个方面非常有启发性,特别是回答了为什么STL中没有使用更多标准OO技术的问题。其中一段话尤其突出:

即使现在的C++继承对于泛型编程也没有太大用处。让我们来探讨一下原因。很多人尝试使用继承来实现数据结构和容器类,但是我们现在知道,几乎没有成功的尝试。C++继承以及与之相关的编程风格受到了极大的限制。使用它来实现包括像等式这样微不足道的东西在内的设计是不可能的。如果你从一个位于继承层次结构根部的基类X开始,并在该类上定义一个接受类型为X的参数的虚等式运算符,然后派生出类Y,那么等式的接口是什么?它具有将Y与X进行比较的等式。以动物为例(面向对象的人喜欢动物),定义哺乳动物并从哺乳动物派生长颈鹿。然后定义一个成员函数mate,其中动物与动物交配并返回一个动物。然后你从动物派生出长颈鹿,当然,它有一个函数mate,其中长颈鹿与动物交配并返回一个动物。这绝对不是你想要的结果。虽然交配对于C++程序员来说可能不是非常重要,但等式却是必不可少的。我不知道有哪个算法不使用某种形式的等式。对于那些更喜欢Java答案的人,Josh Bloch在Effective Java中的第8条目:遵守覆盖时的通用合同中也详细阐述了相同的观点。

这是一个很好的章节,有一个关于尝试保留等式合同的不错示例,同时扩展了一个简单的Point类,并添加了颜色:ColorPoint类。他继续证明了面向对象编程的一个基本限制:没有办法扩展一个可实例化的类并添加值组件,同时保持等式合同

诚然,Bloch的陈述更为简洁,但它们都(正确地)得出了相同的结论。主要区别在于Java是(曾经是)一种“纯OO”语言 - 所有东西都必须驻留在一个类中,即使那些不自然成为对象的算法等东西也是如此。

我认为Bloch可能对这个问题比较敏感,因为他已经在Java库中看到了它的惨痛失败:Stack从Vector继承就是Java中一个显著的设计问题的例子。

稍后在采访中,Stepanov继续说:

我曾经在贝尔实验室参加过几次有关设计模板的讨论,并与Bjarne激烈争论,认为他应该尽可能地使C++模板接近于Ada泛型。我认为我争论得太激烈了,以至于他决定不这样做。我意识到在C++中拥有模板函数的重要性,而不仅仅是一些人所认为的模板类。然而,我认为模板函数应该像Ada泛型一样工作,也就是说,它们应该被明确地实例化。Bjarne没有听我的建议,他设计了一个模板函数机制,其中模板使用重载机制隐式实例化。这种特殊的技术对我的工作非常关键,因为我发现它允许我做许多在Ada中不可能做到的事情。我认为Bjarne的这种设计是一项了不起的工作,我很高兴他没有听从我的建议。

0
为什么不使用图形而不是STL容器,并在节点或边缘上附加所需内容。它们可以模仿任何STL容器(我错了吗?)。您可以轻松地迭代节点或边缘(DFS,BFS),并且沿途可以执行希望在节点和边缘上附加的数据。算法和迭代器的简单混合,不是吗?

0

Dilawar所说的实际上是解决您所有容器需求的方法。

使用Boost::graph或类似的实现。您可以将其用作对象管理系统[这就是我所做的]。

至于对STL的批评,这只是品味问题,而不是技术上的反对意见。这些存在,但不在这个层面上。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接