Extending the C++ Standard Library by inheritance?
通常认为C++标准库一般不打算使用继承来扩展。当然,我(和其他人)批评了那些建议从诸如
所以,我的问题有两部分:
有没有其他的标准库类可以从中派生?
如果一个源于标准库类(如
很好的问题。我真的希望标准能更明确地说明预期的用途。也许应该有一个与语言标准并驾齐驱的C++基础文档。在任何情况下,下面是我使用的方法:
(a)我不知道存在任何此类清单。相反,我使用以下列表来确定标准库类型是否可能被设计为继承自:
- 如果它没有任何
virtual 方法,那么您不应该将它用作基础。这排除了std::vector 等。 - 如果它确实有
virtual 方法,那么它可以作为基类使用。 - 如果有大量的
friend 语句在周围浮动,那么应该避开,因为可能存在封装问题。 - 如果它是一个模板,那么在继承之前仔细观察,因为您可能可以用专门化来定制它。
- 基于政策的机制(如
std::char_traits 的存在)是一个很好的线索,表明你不应该把它作为基础。
不幸的是,我不知道一个好的综合或黑白名单。我通常凭直觉去做。
(b)我会在这里申请LSP。如果有人对你的例外情况打电话给
a)流库被设置为继承:)
关于B部分,从17.3.1.2"要求"第1段:
The library can be extended by a C++ program. Each clause, as applicable, describes the requirements that such extensions must meet. Such extensions are generally one of the following:
- Template arguments
- Derived classes
- Containers, iterators, and/or algorithms that meet an interface convention
虽然17.3是信息性的而不是约束性的,但委员会对派生类行为的意图是明确的。
对于其他非常相似的扩展点,有明确的要求:
- 17.1.15"所需行为"包括替换(操作员新建等)和处理程序功能(终止处理程序等),并将所有不合规行为抛入UB土地。
- "在某些情况下(替换函数、处理函数、用于实例化标准库模板组件的类型的操作),C++标准库依赖于C++程序提供的组件。如果这些组件不符合它们的要求,标准就不会对实现提出任何要求。"
在最后一点上,我不清楚附加说明列表是详尽的,但是考虑到在下一段中如何具体处理每一个提到的案例,如果说当前的文本旨在涵盖派生类,那将是一种延伸。此外,2008年草案(17.6.4.8)中的17.4.3.6/1文本没有改变,我认为解决IT或派生类的虚拟方法没有问题。
C++标准库不是单个单元。这是合并和采用几个不同库的结果(C标准库、iostreams库和stl的一大块是三个主要的构建块,每个构建块都是独立指定的)
正如您所知,库的stl部分通常不打算派生自。它使用通用编程,并且通常避免OOP。
iostreams库是更传统的OOP,在内部大量使用继承和动态多态性,并且用户希望使用相同的机制来扩展它。自定义流通常是通过从流类本身或其内部使用的
就像D.Shawley说的,我会把LSP应用到你的第二个问题上。用派生类替换基类应该总是合法的。如果我称之为
简约规则是"任何类都可以用作基类;在没有虚拟方法(包括虚拟析构函数)的情况下安全使用它的责任完全是派生作者的责任。"在std::exception的子级中添加非pod成员与在std::vector的派生类中添加非pod成员是相同的用户错误。容器不是"有意"作为基础课程的想法是文学教授所谓的权威意图谬误的工程实例。
IS-A原则占主导地位。不要从B派生D,除非D可以在B的公共接口的各个方面(包括B指针上的删除操作)替换B。如果B有虚拟方法,那么这种限制就不那么麻烦了;但是如果B只有非虚拟方法,那么专门化继承仍然是可能的,也是合法的。
C++是多范式的。模板库使用继承,甚至是来自没有虚拟析构函数的类的继承,因此通过示例证明了这种构造是安全和有用的;它们是否是有意的是一个心理问题。
回答问题2):
我相信是的,它们将受到ISO标准的接口描述的约束。例如,该标准允许在全球重新定义
不尊重这一点当然是不明确的行为(至少对斯科特·迈尔斯来说)。我认为我们可以通过对标准库的其他领域的类比来说明这一点。
对于第二个问题,我认为答案是肯定的。标准规定std::exception的what成员必须返回非空值。不管我是否有一个堆栈、引用或指向std::exception的指针值。返回的what()由标准绑定。
当然可以返回空值。但我认为这样的一个等级是不符合标准的。
我知道这个问题由来已久,但我想在此添加我的评论。
从现在开始,我使用了一个从std::string继承的
类cfgValue只是添加了一些构造函数、setter和getter,以便在字符串和数值和布尔值之间快速转换,以及从utf8(保留在std::string中)转换为ucs2(保留在std::wstring中)编码等。
我知道,有许多不同的方法可以做到这一点,但是对于用户来说,它非常方便,而且工作正常(这意味着它不会破坏任何stdlib概念、异常处理等):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } ... }; |
W.R.T.问题2),根据C++标准,派生异常类必须指定一个不抛出即抛出()和返回非空。这意味着在许多情况下,派生的异常类不应使用std::string,因为std::string本身可能会根据实现抛出。