Previous | Table of Contents | Next |
A class can nominate one or more other classes as base classes. If a class D says that B is a base class, we say that D is derived from B. In that case, D will have all the members that B has, as well as any other members that D defines. For example, if we declare
struct B { int x; double y; }; struct D: B { int z; };
then class D has three members: an int called z, which D defined explicitly, and two other members called x and y, which D inherited from its base class B.
As a more complicated example, suppose that we wish to use the String class that we defined earlier to define a new class to represent colored strings, which might be strings that, when displayed, appear in a particular color. Such a class might look like this:
template<class T, class Color> class ColorString: public String<T> { public: // interface members go here private: Color col; };
where the phrase public String<T> says that the fact that ColorString (which is an abbreviation for ColorString<T, Color>) is derived from String<T> should be publicly known. This example shows two common techniques for building new types from existing ones: inheritance and composition. If we have a class RGB that describes a particular way of dealing with colors, we will note that ColorString<char, RGB> has String<char> as a base class, but does not have RGB as a base class. That means that every member of String<char> is also a member of ColorString<char, RGB>, but that members of RGB are not members of ColorString<char, RGB>.
Another way to look at it is that a ColorString is a kind of String, but it is not a kind of Color.
7.5.1.1. Inheritance and Constructors
It is not quite correct to say that every member of a base class is a member of the derived class, because that statement is not true for constructors, destructors, or the assignment operator. Derived class constructors must say how to construct the base class parts of their object. So, for example, if we were going to complete the definition of our ColorString class, we would have to give it a whole new set of constructors, each of which would have to construct the underlying String somehow. If ColorString inherited the constructors of String, then it would be possible to construct a ColorString without touching anything more than the underlying String part, merely by using one of the inherited String constructors.
What C++ does instead is to say that every constructor in a derived class can say how it wants to initialize the base class part with an appropriate base class constructor. It does this by using constructor initializers, which we have already seen, but saying that they should name the base class rather than just naming individual members.
To continue this example in the context of ColorString, imagine that we wanted the default constructor (i.e., the one with no parameters) of ColorString to construct a String with no characters, whose color was the default value of the type parameter Color. Then we might say
template<class T, class Color> class ColorString: public String<T> { public: ColorString() { } ColorString(const String<T>& s): String<T>(s) { } // ... private: Color col; };
The first constructor says nothing about how to initialize the components of a ColorString<T, Color>, so they will be initialized to their default values. The second constructor tells how to construct a ColorString<T, Color> from a String<T>. This constructor says that the String<T> part of the ColorString<T, Color> object should be initialized with the given String<T> value. Because it says nothing about how to initialize the member col, that member will start out with whatever default value an object of class Color ordinarily has. This particular ColorString constructor could also have been written as
ColorString(const String<T>& s): String<T>(s), col(Color()) { }
with the same effect.
In short, every class must describe all the ways in which an object of that class might potentially be constructed; it does not inherit constructors from its base class(es). Each derived class constructor can use the base class constructors to say how to initialize the part of the derived class object that it has in common with its base class.
7.5.1.2. Inheritance and Destructors
Destructors are cumulative, in the sense that anything a derived class destructor says to do will be in addition to whatever the base class destructors do. Therefore, destroying a derived class object executes both the derived class and base class destructors.
In general, that means that a derived class destructor needs to concern itself only with destroying things that were specifically constructed in the derived class destructor; the base class will take care of itself.
7.5.1.3. Inheritance and Assignment
Assignment is slightly unusual, in that the compiler will generate a derived class assignment operator that uses the base class assignment operator(s) if the user does not provide one explicitly. A user who defines a derived class assignment operator must explicitly assign the base class part if that is the desired effect.
Previous | Table of Contents | Next |