通过继承扩展C++标准库?

51 c++ stl

人们普遍认为,C++标准库通常不打算使用继承进行扩展.当然,我(以及其他人)批评那些建议来自诸如此类的人std::vector.但是,这个问题:c ++异常,可以what()为NULL吗?让我意识到标准库至少有一部分是为了扩展 - std::exception.

所以,我的问题有两个部分:

  1. 是否有其他标准库类可以派生自哪些?

  2. 如果一个派生自标准库类,例如std::exception,是否受ISO标准中描述的接口约束?例如,使用异常类的what()成员函数的程序是否会返回NTBS(比如它返回一个空指针)是否符合标准?

D.S*_*ley 38

好问题.我真的希望标准对于预期用途更加明确.也许应该有一个与语言标准并列的C++ Rationale文档.无论如何,这是我使用的方法:

(a)我不知道是否存在任何此类清单.相反,我使用以下列表来确定标准库类型是否可能被设计为继承自:

  • 如果它没有任何virtual方法,那么你不应该使用它作为基础.这排除了std::vector等等.
  • 如果它确实有virtual方法,那么它是用作基类的候选者.
  • 如果有很多friend语句浮动,那么转向清楚,因为可能存在封装问题.
  • 如果它是一个模板,那么在继承它之前要仔细看看,因为你可以用专门化来定制它.
  • 基于策略的机制(例如std::char_traits)的存在是一个非常好的线索,你不应该使用它作为基础.

不幸的是,我不知道一个很好的综合或黑白名单.我通常会感觉到直觉.

(b)我会在这里申请LSP.如果有人调用what()你的异常,那么它的可观察行为应该与之相匹配std::exception.我认为这不是一个标准一致性问题,而是一个正确性问题.标准不要求子类可替代基类.这真的只是一个"最佳实践".

  • 我想补充一点,有些东西意味着通过继承进行小程度的扩展,比如`std :: stack`和`std :: queue`,因为它们有**保护**,因为事情的唯一原因是protected是允许子类读取数据.显然,你需要非常小心你对此做些什么. (3认同)

Ara*_*raK 17

a)流库被继承:)


小智 7

关于你的b部分,从17.3.1.2"要求",第1段:

该库可以通过C++程序进行扩展.每个条款(如果适用)描述了此类扩展必须满足的要求.此类扩展通常是以下之一:

  • 模板参数
  • 派生类
  • 满足接口约定的容器,迭代器和/或算法

虽然17.3是信息性的而不是约束力,但委员会对派生阶级行为的意图是明确的.

对于其他非常类似的扩展点,有明确的要求:

  • 17.1.15"必需行为"包括替换(operator new等)和处理函数(终止处理程序等),并将所有不符合规范的行为抛出到UB-land中.
  • 17.4.3.6/1:"在某些情况下(替换函数,处理函数,用于实例化标准库模板组件的类型的操作),C++标准库依赖于C++程序提供的组件.如果这些组件不符合他们的要求,标准对实施没有要求."

在最后一点,我不清楚括号列表是详尽无遗的,但考虑到下一段中如何具体说明每个提到的案例,可以说当前文本旨在涵盖派生类.此外,该17.4.3.6/1的文字是在2008年的选秀不变(如果它在17.6.4.8),我看不出有什么问题解决IT或派生类的虚拟方法.


Tho*_*day 6

简约的规则是“任何类都可以用作基类;在没有虚拟方法(包括虚拟析构函数)的情况下安全使用它的责任完全由派生作者承担。” 在 std::exception 的子类中添加非 POD 成员与在 std::vector 的派生类中添加非 POD 成员是相同的用户错误。容器并非“旨在”成为基类的想法是文学教授所说的“作者意图谬误”的一个工程示例。

IS-A 原则占主导地位。不要从 B 派生 D,除非 D 可以在 B 的公共接口中的各个方面替代 B,包括 B 指针上的删除操作。如果B有虚方法,这个限制就不那么繁重;但如果 B 只有非虚方法,那么专门化继承仍然是可能且合法的。

C++ 是多范式的。模板库使用继承,甚至从没有虚拟析构函数的类继承,因此通过示例证明此类构造是安全且有用的;是否有意为之是一个心理问题。


jal*_*alf 5

C++标准库不是一个单元.它是组合和采用几个不同库的结果(C标准库的一大块,iostreams库和STL是三个主要构建块,并且每个都是独立指定的)

如您所知,库的STL部分通常不是从中派生出来的.它使用通用编程,通常避免使用OOP.

IOStreams库是更传统的OOP,并且在内部使用继承和动态多态 - 并且期望用户使用相同的机制来扩展它.自定义流通常通过派生自流类本身或streambuf内部使用的类来编写.这两个都具有可以在派生类中重写的虚方法.

std::exception 是另一个例子.

和D.Shawley说的一样,我会将LSP应用到你的第二个问题.将基类替换为派生类应始终是合法的.如果我打电话exception::what(),它必须遵循exception该类指定的合同,无论该exception对象来自何处,或者它是否实际上是一个已被上升的派生类.在这种情况下,该合同是返回NTBS的标准承诺.如果您使派生类的行为不同,那么您违反了标准,因为类型的对象std::exception不再返回NTBS.