简体   繁体   English

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

[英]Extending the C++ Standard Library by inheritance?

It is a commonly held belief that the the C++ Standard library is not generally intended to be extended using inheritance. 通常认为,C ++标准库通常不打算使用继承进行扩展。 Certainly, I (and others) have criticised people who suggest deriving from classes such as std::vector . 当然,我(和其他人)批评了建议从诸如std::vector类的类派生的人。 However, this question: c++ exceptions, can what() be NULL? 但是,这个问题: c ++异常,what()可以为NULL吗? made me realise that there is at least one part of the Standard Library that is intended to be so extended - std::exception . 让我意识到标准库的至少一部分打算如此扩展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? 如果确实从诸如std::exception类的标准库类派生,那么是否受ISO标准中描述的接口约束? 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? 例如,使用异常类的what()成员函数未返回NTBS(例如,它返回了空指针)的程序是否符合标准?

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. 也许应该在语言标准的旁边有一个C ++基本原理文档。 In any case, here is the approach that I use: 无论如何,这是我使用的方法:

(a) I'm not aware of the existence of any such list. (a)我不知道是否存在任何此类清单。 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. 如果它没有任何virtual方法,那么您不应该将其用作基础。 This rules out std::vector and the like. 这排除了std::vector之类的东西。
  • If it does have virtual methods, then it is a candidate for usage as a base class. 如果它确实具有virtual方法,则它可以用作基类。
  • If there are lots of friend statements floating around, then steer clear since there is probably an encapsulation problem. 如果周围有很多friend声明,那么请避免使用,因为可能存在封装问题。
  • 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 (eg, std::char_traits ) is a pretty good clue that you shouldn't be using it as a base. 基于策略的机制(例如std::char_traits )的存在是一个很好的线索,您不应该将其用作基础。

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. (b)我将在这里申请LSP If someone calls what() on your exception, then it's observable behavior should match that of std::exception . 如果有人在您的异常上调用what() ,则它的可观察到的行为应与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" . 这实际上只是“最佳实践”

a)使流库被继承:)

Regarding your part b, from 17.3.1.2 "Requirements", paragraph 1: 关于您的b部分,从17.3.1.2“要求”的第1段开始:

The library can be extended by a C++ program. 该库可以由C ++程序扩展。 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. 尽管17.3提供信息而不是约束力,但委员会对派生阶级行为的意图很明确。

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

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. 此外,该17.4.3.6/1文本在2008年草案中(在17.6.4.8中)保持不变,并且我认为解决该问题或派生类的虚拟方法均无问题

The C++ standard library isn't a single unit. C ++标准库不是一个单元。 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) 这是合并并采用几个不同的库的结果(C标准库的很大一部分,iostreams库和STL是三个主要的构建块,并且每一个都是独立指定的)

The STL part of the library is generally not meant to be derived from, as you know. 如您所知,库的STL部分通常不是从其派生的。 It uses generic programming, and generally avoids OOP. 它使用通用编程,通常避免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. IOStreams库是更传统的OOP,并且在内部大量使用继承和动态多态性-并且希望用户使用相同的机制来扩展它。 Custom streams are typically written by deriving from either the stream class itself, or the streambuf class it uses internally. 自定义流通常是通过从流类本身或其内部使用的streambuf类派生而编写的。 Both of these have virtual methods that can be overridden in derived classes. 它们都具有可以在派生类中重写的虚拟方法。

std::exception is another example. std::exception是另一个示例。

And like D.Shawley said, I would apply the LSP to your second question. 就像D.Shawley所说的那样,我将LSP应用于您的第二个问题。 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. 如果我调用exception::what() ,则无论exception对象来自何处,或者实际上是否已被向上转换,它都必须遵循exception类指定的约定。 And in this case, that contract is the standard's promise of returning a NTBS. 在这种情况下,该合同是标准要求的退还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. 如果使派生类的行为不同,则将违反标准,因为类型为std::exception的对象不再返回NTBS。

To answer question 2): 要回答问题2):

I believe that yes, they would be bound by the interface description of the ISO standard. 我相信是的,它们将受到ISO标准的接口描述的约束。 For example, the standard allows redefining operator new and operator delete globally. 例如,该标准允许在全局范围内重新定义operator newoperator delete However, the standard guarantees that operator delete is a no-operation on null pointers. 但是,该标准保证operator delete对空指针不执行任何操作。

Not respecting this is certainly undefined behaviour (at least to Scott Myers). 不遵守此规定无疑是不确定的行为(至少对于Scott Myers而言)。 I think we can say that the same is true by analogy for other areas of the standard library. 我想可以说,对于类比标准库的其他领域也是如此。

Some of the stuff in functional , like greater<> , less<> , and mem_fun_t are derived from unary_operator<> and binary_operator<> . 一些在东西functional ,如greater<> less<>mem_fun_t衍生自unary_operator<>binary_operator<> But, IIRC, that only gives you some typedefs. 但是,IIRC,这只能为您提供一些typedef。

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. 在std :: exception的子级中添加非POD成员的用户错误与在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. IS-A原则占主导地位。 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. 除非D可以在B的公共接口中的所有方面替代B,包括对B指针的删除操作,否则都不要从B派生D。 If B has virtual methods, this restriction is less onerous; 如果B具有虚拟方法,则此限制不会那么繁重; but if B has only nonvirtual methods, it is still both possible and legitimate to specialize with inheritance. 但是,如果B仅具有非虚拟方法,则专门进行继承仍然是可行且合法的。

C++ is multiparadigmatic. C ++是多范式的。 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. 他们是否有意是一个心理问题。

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. 该标准说std :: exception的what成员必须返回一个非NULL值。 It shouldn't matter if I have a stack, reference or pointer value to std::exception. 是否具有std :: exception的堆栈,引用或指针值都没关系。 The return of what() is bound by the standard. what()的返回受标准约束。

Of course it is possible to return NULL. 当然可以返回NULL。 But I would consider such a class to be non-standards conforming. 但是我认为这样的类不符合标准。

wrt question 2), as per C++ standard, the derived exception-class must specify a no-throw ie throw() specification as well along with returning non-null. 问题2),按照C ++标准,派生的异常类必须指定一个不抛出异常throw()规范)以及返回的非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. 这意味着在许多情况下,派生的异常类不应使用std :: string,因为std :: string本身可能会根据实现而抛出。

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. 从现在开始,我使用了一个从std :: string继承的class CfgValue ,尽管文档(或一些书或一些标准文档,我现在没有方便的来源)说,用户不应该继承std :::。串。 And this class contains a "TODO: remove inheritance from std::string" comment since years. 多年来,此类包含“ TODO:从std :: string中删除继承”注释。

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. CfgValue类仅添加了一些构造函数,setter和getter来在字符串,数字和布尔值之间快速转换,以及从utf8(保留在std :: string中)到ucs2(保留在std :: wstring中)编码等等。

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): 我知道,有很多不同的方法可以做到这一点,但这对用户来说非常方便 ,并且可以正常工作(这意味着,它不会破坏任何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); }
    ...
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM