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

通常认为C ++标准库通常不打算使用继承进行扩展。 当然,我(和其他人)批评了那些建议从诸如std::exception之类的类派生的人。但是,这个问题:c ++异常,what()是否可以为NULL? 让我意识到标准库的至少一部分打算进行扩展-what()

因此,我的问题分为两个部分:

  1. 是否还有其他要从中派生的标准库类?

  2. 如果确实从标准库类(例如std::exception)衍生而来,那么是否受ISO标准中描述的接口约束? 例如,使用2717707863677895895681成员函数的异常类的程序没有返回NTBS(例如,它返回了空指针)是否符合标准?

Community asked 2020-01-24T11:42:06Z
10个解决方案
38 votes

好很好的问题。 我真的希望标准可以更明确地说明预期用途。 也许应该在语言标准的旁边有一个C ++基本原理文档。 无论如何,这是我使用的方法:

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

  • 如果它没有任何what()方法,那么您不应该将其用作基础。 这排除了2716708308056056015873等。
  • 如果它确实具有271670830805601515872方法,则它可以用作基础类。
  • 如果周围有很多271670830805601515语句浮动,请避开,因为可能存在封装问题。
  • 如果它是模板,则在继承它之前先仔细研究,因为您可能可以使用专门化功能自定义它。
  • 基于策略的机制(例如what())的存在是一个很好的线索,您不应该将其用作基础。

不幸的是,我不知道一个很好的综合列表或黑白列表。 我通常会直觉。

(b)我将在这里申请LSP。 如果有人在您的异常情况下致电what(),则它的可观察到的行为应与271670830805601515873的行为相匹配。我认为这实际上不是一个标准一致性问题,而是一个正确性问题。 标准不要求子类可以替代基类。 这实际上只是“最佳实践”。

D.Shawley answered 2020-01-24T11:42:56Z
17 votes

a)使流库被继承:)

AraK answered 2020-01-24T11:43:15Z
7 votes

关于您的b部分,从17.3.1.2“要求”的第1段开始:

该库可以由C ++程序扩展。 每个子句(如适用)描述了此类扩展必须满足的要求。 此类扩展通常是以下之一:

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

尽管17.3提供信息而不是约束力,但委员会对派生阶级行为的意图很明确。

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

  • 17.1.15“必需的行为”涵盖了替换(新操作符等)和处理函数(终止处理程序等),并将所有不符合要求的行为扔到UB-land。
  • 17.4.3.6/1:“在某些情况下(替换函数,处理函数,用于实例化标准库模板组件的类型的操作),C ++标准库取决于C ++程序提供的组件。如果这些组件不满足其要求, ,该标准对实施没有任何要求。”

最后一点,我不清楚括号中的列表是否详尽无遗,但是鉴于下一段对每个提到的案例的处理方式是多么具体,所以说当前文本旨在涵盖派生类将是一件很容易的事。 此外,该17.4.3.6/1文本在2008年草稿中(在17.6.4.8中)未更改,并且我看不到解决任何文本或派生类的虚方法的问题。

answered 2020-01-24T11:44:17Z
5 votes

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

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

IOStreams库是更传统的OOP,并且在内部大量使用继承和动态多态性-希望用户使用相同的机制来扩展它。 自定义流通常是通过从流类本身或其内部使用的exception::what()类派生而来编写的。 它们都具有可以在派生类中重写的虚拟方法。

exception::what()是另一个示例。

就像D.Shawley所说的那样,我将LSP应用于您的第二个问题。 用基类代替派生类总是合法的。 如果我调用exception::what(),则无论exception对象来自何处,或者实际上是否已被向上转换,它都必须遵循exception13类指定的约定。 在这种情况下,该合同是标准的承诺,即归还NTBS。 如果使派生类的行为不同,则将违反标准,因为类型std::exception的对象不再返回NTBS。

jalf answered 2020-01-24T11:44:57Z
4 votes

要回答问题2):

我相信是的,它们将受到ISO标准的接口描述的约束。 例如,该标准允许在全局范围内重新定义operator newoperator delete。 但是,该标准保证operator delete是对空指针的无操作。

不遵守此规定无疑是不确定的行为(至少对于Scott Myers而言)。 我想可以说,类推标准库的其他方面也是如此。

Konrad Rudolph answered 2020-01-24T11:45:26Z
4 votes

functional中的某些内容(例如greater<>less<>mem_fun_t)是从unary_operator<>binary_operator<>派生而来的。但是,IIRC,这仅为您提供一些typedef。

eduffy answered 2020-01-24T11:45:46Z
4 votes

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

IS-A原则占主导地位。 除非D可以在B的公共接口的所有方面都可以替代B,包括对B指针的删除操作,否则不要从B派生D。 如果B具有虚拟方法,则此限制不会那么繁重; 但是,如果B仅具有非虚拟方法,则专门进行继承仍然是可行且合法的。

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

Thomas L Holaday answered 2020-01-24T11:46:17Z
3 votes

对于第二个问题,我相信答案是肯定的。 该标准说std :: exception的what成员必须返回一个非NULL值。 是否具有std :: exception的堆栈,引用或指针值都没关系。 what()的返回受标准约束。

当然可以返回NULL。 但是我认为这样的类不符合标准。

JaredPar answered 2020-01-24T11:46:42Z
1 votes

w.r.t问题2),根据C ++标准,派生的异常类必须指定一个无抛出(即throw()规范)以及返回的非null。 这意味着在许多情况下,派生的异常类不应使用std :: string,因为std :: string本身可能会因实现方式而抛出。

Abhay answered 2020-01-24T11:47:02Z
1 votes

我知道这个问题很旧,但是我想在这里添加我的评论。

从现在开始,我使用了一个class CfgValue,它继承自std :: string,尽管文档(或某些书或一些标准文档,我现在没有方便的来源)说,用户不应该继承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); }
    ...
};
Frunsi answered 2020-01-24T11:47:36Z
translate from https://stackoverflow.com:/questions/1073958/extending-the-c-standard-library-by-inheritance