我经常在 Stack Overflow 上阅读这些声明。就个人而言,我没有发现任何问题,除非我以多态方式使用它;即我必须使用 virtual
析构函数的地方。
如果我想扩展/添加标准容器的功能,那么有什么比继承一个更好的方法呢?将这些容器包装在自定义类中需要更多的努力并且仍然不干净。
原文由 iammilind 发布,翻译遵循 CC BY-SA 4.0 许可协议
我经常在 Stack Overflow 上阅读这些声明。就个人而言,我没有发现任何问题,除非我以多态方式使用它;即我必须使用 virtual
析构函数的地方。
如果我想扩展/添加标准容器的功能,那么有什么比继承一个更好的方法呢?将这些容器包装在自定义类中需要更多的努力并且仍然不干净。
原文由 iammilind 发布,翻译遵循 CC BY-SA 4.0 许可协议
这是一个坏主意的原因有很多。
首先,这是一个坏主意,因为标准容器 没有虚拟析构函数。你永远不应该使用没有虚拟析构函数的多态的东西,因为你不能保证在你的派生类中进行清理。
其次,它是非常糟糕的设计。实际上有几个原因是糟糕的设计。首先,您应该始终通过通用操作的算法来扩展标准容器的功能。这是一个简单的复杂性原因 - 如果您必须为它适用的每个容器编写一个算法,并且您有 M 个容器和 N 个算法,那么您必须编写 M x N 个方法。如果您一般地编写算法,那么您只有 N 个算法。所以你得到更多的重用。
这也是非常糟糕的设计,因为您通过从容器继承来破坏良好的封装。一个好的经验法则是:如果您可以使用类型的公共接口执行所需的操作,则将该新行为置于该类型之外。这改进了封装。如果这是您要实现的新行为,请将其设为命名空间范围函数(如算法)。如果您要强加一个新的不变量,请在类中使用包含。
最后,一般来说,您永远不应该将继承视为扩展类行为的一种手段。这是 早期 OOP 理论的一个大而糟糕的谎言之一,它是由于对重用的不清楚的思考而产生的,即使有一个明确的理论为什么它是坏的,它仍然被教授和推广到今天。当您使用继承来扩展行为时,您将扩展行为绑定到您的接口契约,以将用户的手与未来的变化联系起来。例如,假设您有一个使用 TCP 协议进行通信的 Socket 类型的类,并且您通过从 Socket 派生类 SSLSocket 并在 Socket 之上实现更高 SSL 堆栈协议的行为来扩展它的行为。现在,假设您有一个新的要求,即拥有相同的通信协议,但通过 USB 线路或电话。您需要将所有工作剪切并粘贴到派生自 USB 类或 Telephony 类的新类中。现在,如果你发现了一个错误,你必须在所有三个地方都修复它,这并不总是会发生,这意味着错误需要更长的时间,而且并不总是能得到修复……
这对于任何继承层次结构 A->B->C->… 都是通用的您必须重新设计或重复实施。这导致了非常单一的设计,以后很难改变(想想微软的 MFC,或者他们的 .NET,或者——好吧,他们经常犯这个错误)。相反,您几乎总是应该尽可能地考虑通过组合进行扩展。当您考虑“开放/封闭原则”时,应使用继承。您应该通过继承类拥有抽象基类和动态多态运行时,每个都将完整实现。层次结构不应该很深——几乎总是两个层次。仅当您有不同的动态类别用于需要区分类型安全的各种功能时才使用两个以上。在这些情况下,使用抽象基直到具有实现的叶类。
原文由 ex0du5 发布,翻译遵循 CC BY-SA 3.0 许可协议
3 回答2k 阅读✓ 已解决
2 回答3.9k 阅读✓ 已解决
2 回答3.2k 阅读✓ 已解决
1 回答3.2k 阅读✓ 已解决
1 回答2.7k 阅读✓ 已解决
3 回答3.4k 阅读
1 回答1.6k 阅读✓ 已解决
也许这里的很多人不喜欢这个答案,但现在是时候告诉一些异端邪说了,是的……还要告诉“国王是赤身裸体!”
所有反对推导的动机都很弱。推导与组合没有什么不同。这只是一种“把东西放在一起”的方式。组合将事物放在一起并为其命名,而继承则无需给出明确的名称。
如果您需要一个具有与
std::vector
相同的接口和实现以及更多内容的向量,您可以:使用组合并重写所有嵌入对象函数原型实现委托它们的函数(如果它们是 10000 ……是的:准备重写所有那些 10000)或……
继承它并添加你需要的东西(并且……只需重写构造函数,直到 C++ 律师决定让它们也可继承:我还记得 10 年前狂热者关于“为什么 ctors 不能互相调用”以及为什么它的讨论是“坏坏坏事”……直到 C++11 允许它,突然所有那些狂热者都闭嘴了!)并让新的析构函数不是
virtual
就像在原来的那样。就像每个有 一些
virtual
方法和 一些 没有的类一样,你知道你不能假装调用非virtual
通过寻址基派生的方法,这同样适用于delete
。delete
没有理由假装任何特别的特殊照顾。知道任何不是
virtual
的程序员都不能调用寻址基址,也知道在分配派生后不要在基址上使用delete
。所有的“避免这个”、“不要那样做”,听起来总是对那些天生不可知论者的“道德化”。一种语言的所有特性都是为了解决某个问题而存在的。解决问题的给定方法的好坏取决于上下文,而不是特性本身。
如果您正在做的事情需要为许多容器提供服务,那么继承 可能 不是方式(您必须为所有人重做)。如果是针对特定情况…继承是一种组合方式。忘记 OOP 纯粹主义:C++ 不是“纯 OOP”语言,容器根本不是 OOP。