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:
virtual
methods, then you shouldn't be using it as a base. This rules out std::vector
and the like.virtual
methods, then it is a candidate for usage as a base class.friend
statements floating around, then steer clear since there is probably an encapsulation problem.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".
a) the stream library is made to be inherited :)
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:
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With