人们普遍认为,C ++标准库通常不打算使用继承进行扩展。当然,我(和其他人)批评那些建议来自std::vector
等课程的人。但是,这个问题:c++ exceptions, can what() be NULL?让我意识到标准库中至少有一部分旨在如此扩展 - std::exception
。
所以,我的问题有两个部分:
是否有其他标准库类可以派生自?
如果一个派生自标准库类,例如std::exception
,是否受ISO标准中描述的接口约束?例如,使用what()
成员函数的异常类没有返回NTBS(比如它返回空指针)的程序是标准符合吗?
答案 0 :(得分:38)
好问题。我真的希望标准对于预期用途更加明确。也许应该有一个与语言标准并列的C ++ Rationale文档。无论如何,这是我使用的方法:
(a)我不知道是否存在任何此类清单。相反,我使用以下列表来确定标准库类型是否可能被设计为继承自:
virtual
方法,那么您不应该将它用作基础。这排除了std::vector
之类的内容。virtual
方法,则它可以作为基类使用。friend
语句浮出水面,那么就要明确,因为可能存在封装问题。std::char_traits
)的存在是一个非常好的线索,您不应该将其用作基础。不幸的是,我不知道一个很好的综合或黑白列表。我通常会感到直觉。
(b)我会在这里申请LSP。如果有人在您的异常上调用what()
,则其可观察行为应与std::exception
的行为相匹配。我认为这不是一个标准一致性问题,而是一个正确性问题。标准不要求子类可替代基类。它实际上只是一个“最佳实践”。
答案 1 :(得分:17)
a)使流库继承:)
答案 2 :(得分:7)
关于你的b部分,从17.3.1.2“要求”,第1段:
可以通过C ++程序扩展库。每个条款(如果适用)描述了此类扩展必须满足的要求。此类扩展通常是以下之一:
- 模板参数
- 派生类
- 符合接口约定的容器,迭代器和/或算法
虽然17.3是信息性的而不是约束力,但委员会对衍生类行为的意图是明确的。
对于其他非常类似的扩展点,有明确的要求:
在最后一点,我不清楚括号列表是详尽无遗的,但考虑到下一段中如何具体说明每个提到的案例,可以说当前文本旨在涵盖派生类。另外,17.4.3.6/1文本在2008年草案中没有变化(在17.6.4.8中),我看到没有issues解决它或派生类的虚拟方法。
答案 3 :(得分:5)
C ++标准库不是一个单元。它是组合和采用几个不同库的结果(C标准库的一大块,iostreams库和STL是三个主要构建块,并且每个都是独立指定的)
如您所知,库的STL部分通常不是来自。它使用泛型编程,通常避免使用OOP。
IOStreams库是更传统的OOP,并且在内部使用继承和动态多态 - 并且期望用户使用相同的机制来扩展它。自定义流通常通过从流类本身或其内部使用的streambuf
类派生来编写。这两个都有可以在派生类中重写的虚方法。
std::exception
是另一个例子。
和D.Shawley说的一样,我会将LSP应用于你的第二个问题。将基类替换为派生类应始终是合法的。如果我调用exception::what()
,它必须遵循exception
类指定的合同,无论exception
对象来自何处,或者它是否实际上是已被上升的派生类。在这种情况下,该合同是返回NTBS的标准承诺。如果您使派生类的行为不同,那么您违反了标准,因为std::exception
类型的对象不再返回NTBS。
答案 4 :(得分:4)
回答问题2):
我相信是的,他们会受到ISO标准的界面描述的约束。例如,该标准允许全局重新定义operator new
和operator delete
。但是,该标准保证operator delete
对空指针无操作。
不尊重这一点肯定是未定义的行为(至少对Scott Myers而言)。我想我们可以说,对标准库的其他领域进行类比也是如此。
答案 5 :(得分:4)
functional
中的某些内容(如greater<>
,less<>
和mem_fun_t
)来自unary_operator<>
和binary_operator<>
。但是,IIRC,只给你一些typedef。
答案 6 :(得分:4)
简约规则是“任何类都可以用作基类;在没有虚方法的情况下安全地使用它,包括虚拟析构函数,完全是派生作者的。”在std :: exception的子节点中添加非POD成员与在std :: vector的派生类中的用户错误相同。容器不是“打算”作为基类的想法是文学教授称之为“权威意图谬误”的工程实例。
IS-A原则占主导地位。不要从B派生D,除非D在B的公共接口中可以在每个方面替换B,包括对B指针的删除操作。如果B有虚拟方法,这种限制就不那么繁重了;但是如果B只有非虚方法,那么继承专业化仍然是合法的。
C ++是多范式的。模板库使用继承,甚至从没有虚拟析构函数的类继承,因此通过示例演示这样的构造是安全且有用的;他们是否打算是一个心理问题。
答案 7 :(得分:3)
对于第二个问题,我认为答案是肯定的。标准说std :: exception的成员必须返回非NULL值。如果我有std :: exception的堆栈,引用或指针值,那应该没关系。 what()的返回受标准约束。
当然可以返回NULL。但我认为这样的课程符合非标准。
答案 8 :(得分:1)
w.r.t问题2),根据C ++标准,派生的异常类必须指定无抛出,即 throw()规范以及返回非null。这意味着在许多情况下派生的异常类不应该使用std :: string,因为std :: string本身可能会抛出,具体取决于实现。
答案 9 :(得分:1)
我知道这个问题已经过时了,但我想在这里添加我的评论。
几年以来,我使用了一个继承自std :: string的class CfgValue
,虽然文档(或者一些书或一些标准文档,我现在没有源代码)说,用户应该不从std :: string继承。此类包含“TODO:从std :: string中删除继承”注释多年。
类CfgValue只是添加一些构造函数和setter和getter来快速转换字符串和数值和布尔值,并从utf8(保存在std :: string)转换为ucs2(保存在std :: wstring)编码等等
我知道,有许多不同的方法可以做到这一点,但对用户来说非常方便,并且它工作正常(这意味着,它没有打破任何stdlib概念,异常处理等):
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); }
...
};