可以通过继承来扩展标准库吗?

发布于 2024-07-25 22:36:03 字数 451 浏览 4 评论 0原文

人们普遍认为,C++ 标准库通常不打算使用继承进行扩展。 当然,我(和其他人)批评了那些建议从 std::vector 等类派生的人。 然而,这个问题: Can What() return NULL for exceptions? 让我意识到标准库至少有一部分旨在如此扩展 - std::Exception

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

  1. 是否还有其他旨在派生的标准库类?

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

It is a commonly held belief that the the C++ standard library is not generally intended to be extended using inheritance. Certainly, I (and others) have criticised people who suggest deriving from classes such as std::vector. However, this question: Can what() return NULL for exceptions? made me realise that there is at least one part of the Standard Library that is intended to be so extended - std::exception.

So, my question has two parts:

  1. Are there any other standard library classes which are intended to be derived from?

  2. If one does derive from a standard library class such as std::exception, is one bound by the interface described in the ISO Standard? For example, would a program which used an exception class who's what() member function did not return a NTBS (say it returned a null pointer) be standard conforming?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

初懵 2024-08-01 22:36:03

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

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

  • 如果它没有任何虚拟方法,那么您不应该将其用作一个基地。 这排除了 std::vector 等。
  • 如果它确实具有虚拟方法,那么它就是用作基类的候选者。
  • 如果存在大量 friend 语句,那么请避开,因为可能存在封装问题。
  • 如果它是一个模板,那么在继承它之前请仔细查看,因为您可能可以使用专门化来自定义它。
  • 基于策略的机制(例如,std::char_traits)的存在是一个很好的线索,表明您不应该将其用作基础。

不幸的是,我不知道有一个很好的全面或黑白列表。 我通常凭直觉。

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

Good nice question. I really wish that the Standard was a little more explicit about what the intended usage is. Maybe there should be a C++ Rationale document that sits alongside the language standard. In any case, here is the approach that I use:

(a) I'm not aware of the existence of any such list. Instead, I use the following list to determine whether a Standard Library type is likely to be designed to be inherited from:

  • If it doesn't have any virtual methods, then you shouldn't be using it as a base. This rules out std::vector and the like.
  • If it does have virtual methods, then it is a candidate for usage as a base class.
  • If there are lots of friend statements floating around, then steer clear since there is probably an encapsulation problem.
  • If it is a template, then look closer before you inherit from it since you can probably customize it with specializations instead.
  • The presence of policy-based mechanism (e.g., std::char_traits) is a pretty good clue that you shouldn't be using it as a base.

Unfortunately I don't know of a nice comprehensive or black and white list. I usually go by gut feel.

(b) I would apply LSP here. If someone calls what() on your exception, then it's observable behavior should match that of std::exception. I don't think that it is really a standards conformance issue as much as a correctness issue. The Standard doesn't require that subclasses are substitutable for base classes. It is really just a "best practice".

别想她 2024-08-01 22:36:03

a) 流库是可以继承的:)

a) the stream library is made to be inherited :)

冷情 2024-08-01 22:36:03

关于 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 中),我看不到 问题 解决它或派生类的虚拟方法。

Regarding your part b, from 17.3.1.2 "Requirements", paragraph 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

While 17.3 is informative instead of binding, the committee's intent on derived class behavior is clear.

For other very similar points of extension, there are clear requirements:

  • 17.1.15 "required behavior" covers replacement (operator new, etc.) and handler functions (terminate handlers, etc.) and throws all non-compliant behavior into UB-land.
  • 17.4.3.6/1: "In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ Standard library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation."

In the last point it's not clear to me that the parenthetical list is exhaustive, but given how specifically each mentioned case is dealt with in the next paragraph, it would be a stretch to say the current text was intended to cover derived classes. Additionally, that 17.4.3.6/1 text is unchanged in the 2008 draft (where it's in 17.6.4.8) and I see no issues addressing either it or derived classes' virtual methods.

不乱于心 2024-08-01 22:36:03

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

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

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

The parsimonious rule is "Any class may be be used as a base class; the resposibility for using it safely in the absence of virtual methods, including a virtual destructor, is entirely the deriving author's." Adding a non-POD member in a child of std::exception is the same user-error as it would be in a derived class of std::vector. The idea that the containers are not "intended" to be base classes is an engineering example of what the literature professors call The Fallacy of Authorial Intent.

The IS-A principle dominates. Do not derive D from B unless D can substitute for B in every respect in B's public interface, including the delete operation on a B pointer. If B has virtual methods, this restriction is less onerous; but if B has only nonvirtual methods, it is still both possible and legitimate to specialize with inheritance.

C++ is multiparadigmatic. The template library uses inheritance, even inheritance from classes with no virtual destructors, and thus demonstrates by example that such constructs are safe and useful; whether they were intended is a psychological question.

我也只是我 2024-08-01 22:36:03

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

库的 STL 部分是如您所知,通常并不意味着源自。 它使用泛型编程,并且通常避免 OOP。

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

std::Exception 是另一个例子。

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

The C++ standard library isn't a single unit. It is the result of combining and adopting several different libraries (a large chunk of the C standard library, the iostreams library and the STL are the three main building blocks, and each of these have been specified independently)

The STL part of the library is generally not meant to be derived from, as you know. It uses generic programming, and generally avoids OOP.

The IOStreams library is much more traditional OOP, and uses inheritance and dynamic polymorphism heavily internally --- and users are expected to use the same mechanisms to extend it. Custom streams are typically written by deriving from either the stream class itself, or the streambuf class it uses internally. Both of these have virtual methods that can be overridden in derived classes.

std::exception is another example.

And like D.Shawley said, I would apply the LSP to your second question. It should always be legal to substitute the base class for a derived one. If I call exception::what(), it must follow the contract specified by the exception class, no matter where the exception object came from, or whether it is actually a derived class having been upcasted. And in this case, that contract is the standard's promise of returning a NTBS. If you made a derived class behave differently, then you'd violate the standard because an object of type std::exception no longer returns a NTBS.

请帮我爱他 2024-08-01 22:36:03

回答问题2):

我相信是的,他们会受到ISO标准的接口描述的约束。 例如,该标准允许全局重新定义operator newoperator delete。 但是,该标准保证operator delete 对空指针不进行操作。

不尊重这一点肯定是未定义的行为(至少对斯科特·迈尔斯来说)。 我想我们可以说标准库的其他领域也是如此。

To answer question 2):

I believe that yes, they would be bound by the interface description of the ISO standard. For example, the standard allows redefining operator new and operator delete globally. However, the standard guarantees that operator delete is a no-operation on null pointers.

Not respecting this is certainly undefined behaviour (at least to Scott Myers). I think we can say that the same is true by analogy for other areas of the standard library.

昨迟人 2024-08-01 22:36:03

functions 中的一些内容,如 greater<>less<>mem_fun_t 都是派生而来的来自unary_operator<>binary_operator<>。 但是,IIRC,这只给你一些类型定义。

Some of the stuff in functional, like greater<>, less<>, and mem_fun_t are derived from unary_operator<> and binary_operator<>. But, IIRC, that only gives you some typedefs.

給妳壹絲溫柔 2024-08-01 22:36:03

对于第二个问题,我相信答案是肯定的。 标准规定 std::exception 的 What 成员必须返回非 NULL 值。 如果我有一个指向 std::exception 的堆栈、引用或指针值,那应该不重要。 What() 的返回受标准约束。

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

For the second question I believe the answer is yes. The standard says that the what member of std::exception must return a non-NULL value. It shouldn't matter if I have a stack, reference or pointer value to std::exception. The return of what() is bound by the standard.

Of course it is possible to return NULL. But I would consider such a class to be non-standards conforming.

游魂 2024-08-01 22:36:03

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

几年以来,我使用了一个继承自 std::string 的 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); }
    ...
};

I know this question is old, but I'd like to add my comment here.

Since some years now, I use a class CfgValue which inherits from std::string, though the documentation (or some book or some standard doc, I do not have the source handy now) says, that users should not inherit from std::string. And this class contains a "TODO: remove inheritance from std::string" comment since years.

Class CfgValue just adds some constructors and setters and getters to quickly convert between strings and numeric and boolean values, and conversion from utf8 (kept in std::string) to ucs2 (kept in std::wstring) encoding and so on.

I know, there are many different ways to do this, but it is just extremely handy for the user, and it works fine (which means, it does not break any stdlib concepts, exception handling and the like):

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); }
    ...
};
看春风乍起 2024-08-01 22:36:03

关于问题 2),根据 C++ 标准,派生异常类必须指定无抛出,即 throw() 规范以及返回非空值。 这意味着在许多情况下,派生异常类不应使用 std::string,因为 std::string 本身可能会抛出异常,具体取决于实现。

w.r.t question 2), as per C++ standard, the derived exception-class must specify a no-throw i.e. throw() specification as well along with returning non-null. This means in many cases that the derived exception class should not use std::string, as std::string itself might throw depending on the implementation.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文