通过inheritance扩展C ++标准库?
一般认为C ++标准库通常不是用inheritance来扩展的。 当然,我(和其他人)批评那些build议派生自std::vector
。 但是,这个问题: c ++的exception,可以什么()是NULL? 使我意识到,至less有一部分标准库是打算如此扩展 – std::exception
。
所以,我的问题有两个部分:
-
还有其他的标准库类是从哪里来的?
-
如果从标准库类(如
std::exception
派生,是否由ISO标准中描述的接口绑定? 例如,一个使用exception类的程序谁是what()
成员函数没有返回一个NTBS(说它返回一个空指针)是标准符合?
好问题。 我真的希望标准对于预期用途是什么更明确一些。 也许应该有一个与语言标准并列的C ++ Rationale文档。 无论如何,这里是我使用的方法:
(a)我不知道是否有这样的名单。 相反,我使用以下列表来确定标准库types是否可能被devise为从以下inheritance:
- 如果它没有任何
virtual
方法,那么你不应该使用它作为基础。 这就排除了std::vector
之类的东西。 - 如果它具有
virtual
方法,那么它就是用作基类的候选者。 - 如果有很多
friend
声明在附近,那么转向明确,因为可能是封装问题。 - 如果它是一个模板,那么在inheritance它之前仔细观察一下,因为你可以用专门化来定制它。
- 基于策略的机制(如
std::char_traits
)是一个很好的线索,你不应该把它作为一个基础。
不幸的是,我不知道一个很好的全面或黑白名单。 我通常经过感觉。
(b)我会在这里申请LSP 。 如果有人调用你的exception,那么它的可观察行为应该与std::exception
匹配。 我认为这不是一个真正的标准一致性问题,而是一个正确性问题。 标准并不要求子类可以替代基类。 这实际上只是一个“最佳实践” 。
a)stream库被做成inheritance:)
关于你的b部分,从17.3.1.2“要求”,第1段:
该库可以通过C ++程序进行扩展。 每个条款(如适用)都描述了此类扩展必须满足的要求。 这种扩展通常是以下之一:
- 模板参数
- 派生类
- 符合接口约定的容器,迭代器和/或algorithm
虽然17.3是信息性的而不是约束性的,但委员会关于派生类行为的意图是明确的。
对于其他非常相似的延伸点,有明确的要求:
- 17.1.15“所需行为”涵盖了replace(操作符new等)和处理函数(终止处理程序等),并将所有不符合规定的行为抛入UB-land。
- 17.4.3.6/1:“在某些情况下(replace函数,处理函数,用于实例化标准库模板组件的types的操作),C ++标准库依赖于由C ++程序提供的组件,如果这些组件不符合要求,标准对实施没有要求。“
最后一点,我并不清楚括号内的列表是否详尽无遗,但考虑到下一段将如何具体说明每一个提到的情况,说当前的案文是为了涵盖衍生类别。 另外,17.4.3.6/1的文本在2008年草案(17.6.4.8)中没有改变,我也没有发现处理它或派生类的虚拟方法的问题。
C ++标准库不是一个单一的单元。 它是结合和采用多个不同库的结果(C标准库的大块,iostreams库和STL是三个主要的构build块,每个都是独立指定的)
正如你所知道的,图书馆的STL部分通常不是来源于它的。 它使用generics编程,并且通常避免了OOP。
IOStreams库更传统的OOP,在内部使用inheritance和dynamic多态 – 而且用户需要使用相同的机制来扩展它。 自定义stream通常是通过从stream类本身或其内部使用的streambuf
类派生来编写的。 这两个都有可以在派生类中重写的虚方法。
std::exception
是另一个例子。
就像D.Shawley说的那样,我会把LSP应用到你的第二个问题上。 将基类replace为派生类应始终合法。 如果我调用exception::what()
,它必须遵循exception
类指定的契约,不pipeexception
对象来自何处,或者它是否实际上是一个派生类已被上传。 在这种情况下,这个合同是标准的退还NTBS的承诺。 如果您使派生类的行为不同,那么您将违反标准,因为std::exception
types的对象不再返回NTBS。
回答问题2):
我相信是的,他们会受到ISO标准的接口描述的约束。 例如,该标准允许全局重新定义operator new
和operator delete
。 但是,标准保证operator delete
是空指针上的无操作。
不尊重这一点肯定是不明确的行为(至less对斯科特·迈尔斯)。 我想我们可以说,标准图书馆其他领域的类推也是如此。
一些functional
中的东西,比如greater<>
, less<>
和mem_fun_t
是从unary_operator<>
和binary_operator<>
派生的。 但是,IIRC,只给你一些types定义。
吝啬的规则是:“任何类都可以作为基类;在没有虚拟方法的情况下安全地使用它,包括虚拟析构函数,完全是派生作者的”。 在std :: exception的子项中添加非POD成员与在std :: vector的派生类中出现相同的用户错误。 容器不是“打算”成为基类的观点是文学教授所称的“权威谬误”的一个工程实例。
IS-A原则占主导地位。 除非D可以在B的公共接口的每个方面replaceB,包括对B指针的删除操作,否则不要从B派生D. 如果B有虚拟方法,这个限制就不那么繁重; 但是如果B只有非虚方法,inheritance和inheritance是合理的。
C ++是多派生的。 模板库使用inheritance,甚至从没有虚拟析构函数的类inheritance,从而通过示例演示这样的构造是安全和有用的; 他们是否打算是一个心理问题。
对于第二个问题,我相信答案是肯定的。 标准说std :: exception的成员必须返回一个非NULL值。 不要紧,如果我有一个堆栈,引用或指针值std :: exception。 什么()的返回是由标准的约束。
当然可以返回NULL。 但是我认为这样的课是不符合标准的。
关于问题2),按照C ++标准,派生的exception类必须指定一个无抛出的即抛出()规范以及返回非空。 这意味着在许多情况下,派生的exception类不应该使用std :: string,因为std :: string本身可能会根据实现而抛出。
我知道这个问题是旧的,但我想在这里添加我的评论。
几年以来,我使用从std :: stringinheritance的class CfgValue
,尽pipe文档(或者某些书或者一些标准的doc,我现在没有这个源代码)说,用户不应该inheritancestd ::串。 这个类包含了一个“TODO:从std :: string中删除inheritance”的注释。
类CfgValue只是添加了一些构造函数和设置器和getter来快速转换string和数字和布尔值之间的转换,并从utf8(保存在std :: string)到ucs2(保存在std :: wstring)编码等等。
我知道,有很多不同的方法可以做到这一点,但它对用户来说非常方便 ,并且工作正常(这意味着它不会破坏任何stdlib概念,exception处理等):
class CfgValue : public std::string { public: ... CfgValue( const int i ) : std::string() { SetInteger(i); } ... void SetInteger( int i ); ... int GetInteger() const; ... operator std::wstring() { return utf8_to_ucs16(*this); } operator std::wstring() const { return utf8_to_ucs16(*this); } ... };