main atthe top, followed by a level of functions which are called frommain,etc..InC++ therelationship between code and data is also frequentlydefined in terms of dependencies amongclasses. This looks likecomposition (see section7.3), where objects of a classcontain objects of another class as their data. But the relation describedhere is of a different kind: a class can bedefined in terms of an older,pre-existing, class. This produces a new class having all the functionality ofthe older class, and additionally defining its own specific functionality.Instead of composition, where a given classcontains another class, wehere refer toderivation, where a given classis oris-implemented-in-terms-of another class.
Another term for derivation isinheritance: the new class inherits thefunctionality of an existing class, while the existing class does not appearas a data member in the interface of the new class. When discussinginheritance the existing class is called thebase class, while the newclass is called thederived class.
Derivation of classes is often used when the methodology ofC++ programdevelopment is fully exploited. In this chapter we first address thesyntactic possibilities offered byC++ for deriving classes. Followingthis we address some of the specific possibilities offered byclass derivation (inheritance).
As we have seen in the introductory chapter (see section2.4), in theobject-oriented approach to problem solving classes are identified during theproblem analysis. Under this approach objects of the defined classesrepresent entities that can be observed in the problem at hand. The classesare placed in a hierarchy, with the top-level class containing limitedfunctionality. Each new derivation (and hence descent in theclass hierarchy) adds new functionality compared to yet existing classes.
In this chapter we shall use a simple vehicle classification system to build ahierarchy of classes. The first class isVehicle, which implements as itsfunctionality the possibility to set or retrieve the mass of a vehicle. Thenext level in the object hierarchy are land-, water- and air vehicles.
The initialobject hierarchy is illustrated in Figure15.

This chapter mainly focuses on the technicalities of class derivation. Thedistinction between inheritance used to create derived classes whose objectsshould be considered objects of the base class and inheritance used toimplement derived classesin-terms-of their base classes is postponeduntil the next chapter (14).
Inheritance (and polymorphism, cf. chapter14) can be used withclasses and structs. It is not defined for unions.
Car is a special case of aLand vehicle, which in turn is aspecial case of aVehicle.The classVehicle represents the `greatest common divisor' in theclassification system.Vehicle is given limited functionality: it canstore and retrieve a vehicle's mass:
class Vehicle { size_t d_mass; public: Vehicle(); Vehicle(size_t mass); size_t mass() const; void setMass(size_t mass); };Using this class, the vehicle's mass can be defined as soon as thecorresponding object has been created. At a later stage the mass can bechanged or retrieved.
To represent vehicles traveling over land, a new classLand can bedefined offeringVehicle's functionality and adding its own specificfunctionality. Assume we are interested in the speed of land vehiclesandin their mass. The relationship betweenVehicles andLands could ofcourse be represented by composition but that would be awkward: compositionsuggests that aLand vehicleis-implemented-in-terms-of, i.e.,contains, aVehicle, while the natural relationship clearly is thattheLand vehicleis a kind ofVehicle.
A relationship in terms of composition would also somewhat complicate ourLand class's design. Consider the following example showing a classLand using composition (only thesetMass functionality is shown):
class Land { Vehicle d_v; // composed Vehicle public: void setMass(size_t mass); }; void Land::setMass(size_t mass) { d_v.setMass(mass); }Using composition, theLand::setMass function only passes itsargument on toVehicle::setMass. Thus, as far as mass handling isconcerned,Land::setMass introduces no extra functionality, just extracode. Clearly this code duplication is superfluous: aLand objectis aVehicle; to state that aLand objectcontains aVehicle is atleast somewhat peculiar.
The intended relationship is represented better byinheritance. Arule of thumb for choosing between inheritance and compositiondistinguishes betweenis-a andhas-a relationships. A truckis avehicle, soTruck should probably derive fromVehicle. On the otherhand, a truckhas an engine; if you need to model engines in your system,you should probably express this by composing anEngine class inside theTruck class.
Following the above rule of thumb,Land isderived from the base classVehicle:
class Land: public Vehicle { size_t d_speed; public: Land(); Land(size_t mass, size_t speed); void setSpeed(size_t speed); size_t speed() const; };To derive a class (e.g.,Land) from another class (e.g.,Vehicle)postfix the class nameLand in its interface by: public Vehicle:
class Land: public Vehicle
The classLand now contains all the functionality of its base classVehicle as well as its own features. Here those features are a constructorexpecting two arguments and member functions to access thed_speed datamember. Here is an example showing the possibilities of the derived classLand:
Land veh{ 1200, 145 }; int main() { cout << "Vehicle weighs " << veh.mass() << ";\n" "its speed is " << veh.speed() << '\n'; }This example illustrates two features of derivation.
mass is not mentioned as a member inLand'sinterface. Nevertheless it is used inveh.mass. This member function isan implicit part of the class, inherited from its `parent' vehicle.Land contains thefunctionality ofVehicle, theVehicle's private members remainprivate: they can only be accessed byVehicle's own member functions. Thismeans thatLand's member functionsmust useVehicle's memberfunctions (likemass andsetMass) to address themassfield. Here there's no difference between the access rights granted toLand and the access rights granted to other code outside of the classVehicle. The classVehicleencapsulates thespecificVehicle characteristics, anddata hiding is one way to realizeencapsulation.Encapsulation is a core principle of good class design. Encapsulationreduces the dependencies among classes improving the maintainability andtestability of classes and allowing us to modify classes without the need tomodify depending code. By strictly complying with the principle of data hidinga class's internal data organization may change without requiring dependingcode to be changed as well. E.g., a classLines originally storingC-strings could at some point have its data organization changed. It couldabandon itschar ** storage in favor of avector<string> basedstorage. WhenLines uses perfect data hiding depending source codemay use the newLines class without requiring any modification at all.
As arule of thumb, derived classes must be fully recompiled (but don'thave to be modified) when thedata organization (i.e., the data members)of their base classes change. Adding new memberfunctions to the baseclass doesn't alter the data organization so norecompilation is neededwhen new memberfunctions are added.
There is one subtle exception to this rule of thumb: if a new member functionis added to a base class and that function happens to be declared as the firstvirtual member function of the base class (cf. chapter14for a discussion of the virtual member function concept) then thatalso changes the data organization of the base class.
Now thatLand has been derived fromVehicle we're ready for our nextclass derivation. We'll define a classCar to representautomobiles. Agreeing that aCar object is aLand vehicle, and thataCar has a brand name it's easy to design the classCar:
class Car: public Land { std::string d_brandName; public: Car(); Car(size_t mass, size_t speed, std::string const &name); std::string const &brandName() const; };In the above class definition,Car was derived fromLand, which inturn is derived fromVehicle. This is callednested derivation:Land is calledCar'sdirect base class, whileVehicle iscalledCar'sindirect base class.
Car has been derived fromLand andLand has been derivedfromVehicle we might easily be seduced into thinking that these classhierarchies are the way to go when designing classes. But maybe we shouldtemper our enthusiasm.Repeatedly deriving classes from classes quickly results in big, complex classhierarchies that are hard to understand, hard to use and hard tomaintain. Hard to understand and use as users of our derived class now alsohave to learn all its (indirect) base class features as well. Hard to maintainbecause all those classes are very closely coupled. While it may be true thatwhen data hiding is meticulously adhered to derived classes do not have to bemodified when their base classes alter their data organization, it alsoquickly becomes practically infeasible to change those base classes once moreand more (derived) classes depend on their current organization.
What initially looks like a big gain, inheriting the base class's interface,thus becomes a liability. The base class's interface is hardly ever completelyrequired and in the end a class may benefit from explicitly defining its ownmember functions rather than obtaining them through inheritance.
Often classes can be definedin-terms-of existing classes: some of theirfeatures are used, but others need to be shielded off. Consider thestackcontainer: it is commonly implemented in-terms-of adeque, returningdeque::back's value asstack::top's value.
When using inheritance to implement anis-a relationship make sure to getthe `direction of use' right: inheritance aiming at implementing anis-arelationship should focus on the base class: the base class facilities aren'tthere to be used by the derived class, but the derived class facilities shouldredefine (reimplement) the base class facilities using polymorphism (which isthe topic of thenext chapter), allowingcode to use the derived class facilities polymorphically through the baseclass. We've seen this approach when studying streams: the base class (e.g.,ostream) is used time and again. The facilities defined by classes derivedfromostream (likeofstream andostringstream) are then used bycode only relying on the facilities offered by theostream class, neverusing the derived classes directly.
When designing classes always aim at the lowest possible coupling. Big classhierarchies usually indicate poor understanding of robust class design. When aclass's interface is only partially used and if the derived class isimplemented in terms of another class consider using composition rather thaninheritance and define the appropriate interface members in terms of themembers offered by the composed objects.
The keywordprivate starts sections in class interfaces in which membersare declared which can only be accessed by members of the class itself. Thisis our main tool for realizing data hiding. According to established goodpractices of class design the public sections are populated with memberfunctions offering a clean interface to the class's functionality. Thesemembers allow users to communicate with objects; leaving it to the objects howrequests sent to objects are handled. In a well-designed class its objects arein full control of their data.
Inheritance doesn't change these principles, nor does it change the way the`private' and `protected' keywords operate. A derived class does nothave access to a base class's private section.
Sometimes this is a bit too restrictive. Consider a class implementing arandom number generatingstreambuf (cf. chapter6). Such astreambuf can be used to construct anistream irand, after whichextractions fromirand produces series of random numbers, like in the nextexample in which 10 random numbers are generated using stream I/O:
RandBuf buffer; istream irand(&buffer); for (size_t idx = 0; idx != 10; ++idx) { size_t next; irand >> next; cout << "next random number: " << next << '\n'; }The question is, how many random numbers shouldirand be able togenerate? Fortunately, there's no need to answer this question, asRandBuf can be made responsible for generating the next randomnumber. RandBuf, therefore, operates as follows:
streambuf;istream object extracts this random number, merely usingstreambuf's interface;OnceRandBuf has stored the text representation of the nextrandom number in some buffer, it must tell its base class (streambuf)where to find the random number's characters. For thisstreambuf offers amembersetg, expecting the location and size of the buffer holding therandom number's characters.
The membersetg clearly cannot be declared instreambuf'sprivate section, asRandBuf must use it to prepare for theextraction of the next random number. But it should also not be instreambuf's public section, as that could easily result in unexpectedbehavior byirand. Consider the following hypothetical example:
RandBuf randBuf; istream irand(&randBuf); char buffer[] = "12"; randBuf.setg(buffer, ...); // setg public: buffer now contains 12 size_t next; irand >> next; // not a *random* value, but 12.
Clearly there is a close connection betweenstreambuf and its derivedclassRandBuf. By allowingRandBuf to specify the buffer fromwhichstreambuf reads charactersRandBuf remains in control,denying other parts of the program to break its well-defined behavior.
This close connection between base- and derived-classes is realized by a thirdkeyword related to the accessibility of class members:protected. Here ishow the membersetg could have been be declared in a classstreambuf:
class streambuf { // private data here (as usual) protected: void setg(... parameters ...); // available to derived classes public: // public members here };Protected members are members that can be accessed by derived classes, but arenot part of a class's public interface.
Avoid the temptation to declaredata members in a class's protectedsection: it's a sure sign of bad class design as it needlessly results intight coupling of base and derived classes. The principle ofdata hidingshould not be abandoned now that the keywordprotected has beenintroduced. If a derived class (but not other parts of the software) shouldbe given access to its base class's data, use member functions:accessors and modifiers declared in the base class's protectedsection. This enforces the intended restricted access without resulting intightly coupled classes.
Protected derivation is used when the keywordprotected is put in front ofthe derived class's base class:
class Derived: protected Base
When protected derivation is used all the base class's public andprotected members become protected members in the derived class. The derivedclass may access all the base class's public and protected members. Classesthat are in turn derived from the derived class view the base class's membersas protected. Any other code (outside of the inheritance tree) is unable toaccess the base class's members.
Private derivation is used when the keywordprivate is put in front of thederived class's base class:
class Derived: private Base
When private derivation is used all the base class's members turn intoprivate members in the derived class. The derived class members may accessall base class public and protected members but base class members cannot beused elsewhere.
Public derivation should be used to define anis-a relationshipbetween a derived class and a base class: the derived class objectis-abase class object allowing the derived class object to be used polymorphicallyas a base class object in code expecting a base class object. Privateinheritance is used in situations where a derived class object is definedin-terms-of the base class where composition cannot be used. There's littledocumented use for protected inheritance, but one could maybe encounterprotected inheritance when defining a base class that is itself a derivedclass making its base class members available to classes derived from it.
Combinations of inheritance types do occur. For example, when designing astream-class it is usually derived fromstd::istream orstd::ostream. However, before a stream can be constructed, astd::streambuf must be available. Taking advantage of the fact that theinheritance order is defined in the class interface, we use multipleinheritance (see section13.6) to derive the class from bothstd::streambuf and (then) fromstd::ostream. To the class's users itis astd::ostream and not astd::streambuf. So private derivation isused for the latter, and public derivation for the former class:
class Derived: private std::streambuf, public std::ostream
In some situations this scheme is too restrictive. Consider a classRandStream derived privately from aclassRandBuf which is itself derived fromstd::streambuf and alsopublicly fromistream:
class RandBuf: public std::streambuf { // implements a buffer for random numbers }; class RandStream: private RandBuf, public std::istream { // implements a stream to extract random values from };Such a class could be used to extract, e.g., random numbers using thestandardistream interface.
Although theRandStream class is constructed with thefunctionality ofistream objects in mind, some of the members of the classstd::streambuf may be considered useful by themselves. E.g., the functionstreambuf::in_avail returns a lower bound to the number of charactersthat can be read immediately. The standard way to make this function availableis to define ashadow member calling the base class's member:
class RandStream: private RandBuf, public std::istream { // implements a stream to extract random values from public: std::streamsize in_avail(); }; inline std::streamsize RandStream::in_avail() { return std::streambuf::in_avail(); }This looks like a lot of work for just making available a member from theprotected or private base classes. If the intent is to make available thein_avail memberaccess promotion can be used. Access promotion allows us to specify whichmembers of private (or protected) base classes become available in theprotected (or public) interface of the derived class. Here is the aboveexample, now using access promotion:
class RandStream: private RandBuf, public std::istream { // implements a stream to extract random values from public: using std::streambuf::in_avail; };It should be noted that access promotion makes available all overloadedversions of the declared base class member. So, ifstreambuf would offernot onlyin_avail but also, e.g.,in_avail(size_t *)bothmembers would become part of the public interface.
A constructor exists to initialize the object's data members. A derived classconstructor is also responsible for the proper initialization of its baseclass. Looking at the definition of the classLand introduced earlier(section13.1), its constructor could simply be defined asfollows:
Land::Land(size_t mass, size_t speed) { setMass(mass); setSpeed(speed); }However, this implementation has several disadvantages.
const data members must be initialized. In those cases a specialized baseclass constructor must be used instead of the base class default constructor.Land'sconstructor may therefore be improved: Land::Land(size_t mass, size_t speed) : Vehicle(mass), d_speed(speed) {}Derived class constructors always by default call their base class'sdefault constructor. This is of course not correct for a derived class'scopy constructor. Assuming that the classLand must be provided with acopy constructor itsLand const &other parameter also represents the otherobject's base class:
Land::Land(Land const &other) // assume a copy constructor is needed : Vehicle(other), // copy-construct the base class part. d_speed(other.d_speed) // copy-construct Land's data members {}The design of move constructors moving data members was covered in section9.7. A move constructor for a derived class whose base class ismove-aware mustanonymize the rvalue reference before passing it to thebase class move constructor. Thestd::move function should be used whenimplementing the move constructor to move the information in base classes orcomposed objects to their new destination object.
The first example shows the move constructor for the classCar,assuming it has a movablechar *d_brandName data member andassuming thatLand is a move-aware class. The second example shows themove constructor for the classLand, assuming that it does not itself havemovable data members, but that itsVehicle base class is move-aware:
Car::Car(Car &&tmp) : Land(std::move(tmp)), // anonymize `tmp' d_brandName(tmp.d_brandName) // move the char *'s value { tmp.d_brandName = 0; } Land(Land &&tmp) : Vehicle(std::move(tmp)), // move-aware Vehicle d_speed(tmp.d_speed) // plain copying of plain data {}Car this could boil down to: Car &Car::operator=(Car &&tmp) { swap(tmp); return *this; }If swapping is not supported thenstd::move can be used to call thebase class's move assignment operator:
Car &Car::operator=(Car &&tmp) { static_cast<Land &>(*this) = std::move(tmp); // move Car's own data members next return *this; }This feature is either used or not. It is not possible to omit some of thederived class constructors, using the corresponding base class constructorsinstead. To use this feature for classes that are derived from multiple baseclasses (cf. section13.6) all the base class constructors musthave different signatures. Considering the complexities that are involved hereit's probably best to avoid using base class constructors for classes usingmultiple inheritance.
The construction of derived class objects can be delegated to base class constructor(s) usingthe following syntax:
class BaseClass { public: // BaseClass constructor(s) }; class DerivedClass: public BaseClass { public: using BaseClass::BaseClass; // No DerivedClass constructors // are defined }; struct Base { int value; }; struct Derived: public Base { string text; }; // Initializiation of a Derived object: Derived der{{value}, "hello world"}; // ------- // initialization of Derived's base struct. class Base { public: ~Base(); }; class Derived: public Base { public: ~Derived(); }; int main() { Derived derived; }At the end ofmain, thederived object ceases to exists. Hence,its destructor (~Derived) is called. However, sincederived is also aBase object, the~Base destructor is called as well. The base classdestructor is never explicitly called from the derived class destructor.
Constructors and destructorsare called in a stack-like fashion: whenderived is constructed, theappropriate base class constructor is called first, then the appropriatederived class constructor is called. When the objectderived is destroyed,its destructor is called first, automatically followed by the activation oftheBase class destructor. Aderived class destructor is always calledbefore itsbase class destructor is called.
When the construction of a derived class object did not successfully complete(i.e., the constructor threw an exception) then its destructor is notcalled. However, the destructors of properly constructed base classeswillbe called if a derived class constructor throws an exception. This, of course,is how it should be: a properly constructed object should also be destroyed,eventually. Example:
#include <iostream> struct Base { ~Base() { std::cout << "Base destructor\n"; } }; struct Derived: public Base { Derived() { throw 1; // at this time Base has been constructed } }; int main() { try { Derived d; } catch(...) {} } /* This program displays `Base destructor' */mass functionshould return the combined mass.The definition of aTruck starts with a class definition. Our initialTruck class is derived fromCar but it is then expanded to hold onemoresize_t field representing the additional mass information. Here wechoose to represent the mass of the tractor in theCar class and to storethe mass of a full truck (tractor + trailer) in its ownd_mass datamember:
class Truck: public Car { size_t d_mass; public: Truck(); Truck(size_t tractor_mass, size_t speed, char const *name, size_t trailer_mass); void setMass(size_t tractor_mass, size_t trailer_mass); size_t mass() const; }; Truck::Truck(size_t tractor_mass, size_t speed, char const *name, size_t trailer_mass) : Car(tractor_mass, speed, name), d_mass(tractor_mass + trailer_mass) {}Note that the classTruck now contains two functions alreadypresent in the base classCar:setMass andmass.
setMass poses no problems: thisfunction is simply redefined to perform actions which are specific to aTruck object.setMass, however,hidesCar::setMass. For aTruck only thesetMass function havingtwosize_t arguments can be used.Vehicle'ssetMass function remains available for aTruck, but it must now be calledexplicitly, asCar::setMass is hidden from view. This latter function is hidden,even thoughCar::setMass has only onesize_t argument. To implementTruck::setMass we could write:void Truck::setMass(size_t tractor_mass, size_t trailer_mass){ d_mass = tractor_mass + trailer_mass; Car::setMass(tractor_mass); // note: Car:: is required}Car::setMass isaccessed using thescope resolution operator. So, if aTruck truck needsto set itsCar mass, it must usetruck.Car::setMass(x);
class Truck:// in the interface:void setMass(size_t tractor_mass);// below the interface:inline void Truck::setMass(size_t tractor_mass){ (d_mass -= Car::mass()) += tractor_mass; Car::setMass(tractor_mass);}Now the single argumentsetMass member function can be used byTruck objects without using the scope resolution operator. As thefunction is defined inline, no overhead of an additional function call isinvolved.
using declaration may be added tothe derived class interface. The relevant section ofTruck's classinterface then becomes:class Truck: public Car{ public: using Car::setMass; void setMass(size_t tractor_mass, size_t trailer_mass);};A using declaration imports (all overloaded versions of) the mentionedmember function directly into the derived class's interface. If a base classmember has a signature that is identical to a derived class member thencompilation fails (ausing Car::mass declaration cannot be added toTruck's interface). Now code may usetruck.setMass(5000) as well astruck.setMass(5000, 2000).
Using declarations obey access rights. To prevent non-class members fromusingsetMass(5000) without a scope resolution operator but allowingderived class members to do so theusing Car::setMass declarationshould be put in the classTruck's private section.
mass is also already defined inCar, asit was inherited fromVehicle. In this case, the classTruckredefines this member function to return the truck's full mass:size_t Truck::mass() const{ return d_mass;} int main() { Land vehicle{ 1200, 145 }; Truck lorry{ 3000, 120, "Juggernaut", 2500 }; lorry.Vehicle::setMass(4000); cout << '\n' << "Tractor weighs " << lorry.Vehicle::mass() << '\n' << "Truck + trailer weighs " << lorry.mass() << '\n' << "Speed is " << lorry.speed() << '\n' << "Name is " << lorry.name() << '\n'; }The classTruck was derived fromCar. However, one might questionthis class design. Since a truck is conceived of as a combination of atractor and a trailer it is probably better defined using a mixed design,using inheritance for the tractor part (inheriting fromCar, andcomposition for the trailer part).
This redesign changes our point of view from aTruckbeing aCar(and some strangely added data members) to aTruck stillbeing anCar (the tractor) andcontaining aVehicle (the trailer).
Truck's interface is now very specific, not requiring users to studyCar's andVehicle's interfaces and it opens up possibilities fordefining `road trains': tractors towing multiple trailers. Here is an exampleof such an alternate class setup:
class Truck: public Car // the tractor { Vehicle d_trailer; // use vector<Vehicle> for road trains public: Truck(); Truck(size_t tractor_mass, size_t speed, char const *name, size_t trailer_mass); void setMass(size_t tractor_mass, size_t trailer_mass); void setTractorMass(size_t tractor_mass); void setTrailerMass(size_t trailer_mass); size_t tractorMass() const; size_t trailerMass() const; // consider: Vehicle const &trailer() const; };Randbuf classes thus far have always been derivedfrom a single base class. In addition tosingle inheritanceC++ also supportsmultiple inheritance. In multiple inheritance aclass is derived from several base classes and hence inherits functionalityfrom multiple parent classes at the same time.When using multiple inheritance it should be defensible to consider thenewly derived class an instantiation of both base classes. Otherwise,composition is more appropriate. In general, linear derivation (using onlyone base class) is used much more frequently than multiple derivation. Goodclass design dictates that a class should have a single, well describedresponsibility and that principle often conflicts with multiple inheritancewhere we can state that objects of classDerived arebothBase1andBase2 objects.
But then, considerthe prototype of an object for whichmultiple inheritance was used to its extreme: theSwiss army knife! This objectis a knife, itis a pair ofscissors, itis a can-opener, itis a corkscrew, itis ....
The `Swiss army knife' is an extreme example of multiple inheritance. InC++ thereare various good arguments for using multiple inheritance aswell, without violating the `one class, one responsibility' principle. Wepostpone those arguments until thenext chapter. Thecurrent section concentrates on the technical details of constructing classesusing multiple inheritance.
How to construct a `Swiss army knife' inC++? First we need (at least)two base classes. For example, let's assume we are designing a toolkitallowing us to construct an instrument panel of an aircraft's cockpit. Wedesign all kinds of instruments, like an artificial horizon and analtimeter. One of the components that is often seen in aircraft is anav-com set: a combination of a navigational beacon receiver (the `nav'part) and a radio communication unit (the `com'-part). To define the nav-comset, we start by designing theNavSet class (assume the existence of theclassesIntercom, VHF_Dial andMessage):
class NavSet { public: NavSet(Intercom &intercom, VHF_Dial &dial); size_t activeFrequency() const; size_t standByFrequency() const; void setStandByFrequency(size_t freq); size_t toggleActiveStandby(); void setVolume(size_t level); void identEmphasis(bool on_off); };Next we design the classComSet:
class ComSet { public: ComSet(Intercom &intercom); size_t frequency() const; size_t passiveFrequency() const; void setPassiveFrequency(size_t freq); size_t toggleFrequencies(); void setAudioLevel(size_t level); void powerOn(bool on_off); void testState(bool on_off); void transmit(Message &message); };Using objects of this class we can receive messages, transmittedthough theIntercom, but we can alsotransmit messages using aMessage object that's passed to theComSet object using itstransmit member function.
Now we're ready to construct ourNavCom set:
class NavComSet: public ComSet, public NavSet { public: NavComSet(Intercom &intercom, VHF_Dial &dial); };Done. Now we have defined aNavComSet which isboth aNavSetand aComSet: the facilities of both base classes are nowavailable in the derived class using multiple inheritance.
Please note the following:
public is present before both base class names(NavSet andComSet). By default inheritance usesprivate derivation and the keywordpublic must be repeated beforeeach of the base class specifications. Base classes are not required to usethe same derivation type. One base class could havepublic derivation andanother base class could useprivate derivation.NavComSet introduces no additionalfunctionality of its own, but merely combines two existing classes into a newaggregate class. Thus,C++ offers the possibility to simply sweepmultiple simple classes into one more complex class.NavComSetconstructor:NavComSet::NavComSet(Intercom &intercom, VHF_Dial &dial): ComSet(intercom), NavSet(intercom, dial){}The constructor requires no extra code: Its purpose is to activatethe constructors of its base classes. The order in which the base classinitializers are called isnotdictated by their calling order in the constructor's code, but by the orderingof the base classes in the class interface.
NavComSet class definition requires no additional datamembers or member functions: here (and often) the inherited interfaces provideall the required functionality and data for the multiply derived class tooperate properly.setVolume in theNavSet class and a functionsetAudioLevel in theComSet class. A bit cheating, since we could expect that both units infact have a composed objectAmplifier, handling the volume setting. Arevised class might offer anAmplifier &lifier() const member function,and leave it to the application to set up its own interface to theamplifier. Alternatively, a revised class could define members for setting thevolume of either theNavSet or theComSet parts.In situations where two base classes offer identically named membersspecial provisions need to be made to prevent ambiguity:
NavComSet navcom(intercom, dial);navcom.NavSet::setVolume(5); // sets the NavSet volume levelnavcom.ComSet::setVolume(5); // sets the ComSet volume level
inline:class NavComSet: public ComSet, public NavSet{ public: NavComSet(Intercom &intercom, VHF_Dial &dial); void comVolume(size_t volume); void navVolume(size_t volume);};inline void NavComSet::comVolume(size_t volume){ ComSet::setVolume(volume);}inline void NavComSet::navVolume(size_t volume){ NavSet::setVolume(volume);}NavComSet class is obtained from a third party, and cannotbe modified, a disambiguatingwrapper class may be used:class MyNavComSet: public NavComSet{ public: MyNavComSet(Intercom &intercom, VHF_Dial &dial); void comVolume(size_t volume); void navVolume(size_t volume);};inline MyNavComSet::MyNavComSet(Intercom &intercom, VHF_Dial &dial): NavComSet(intercom, dial);{}inline void MyNavComSet::comVolume(size_t volume){ ComSet::setVolume(volume);}inline void MyNavComSet::navVolume(size_t volume){ NavSet::setVolume(volume);}NavCom class, introduced in section13.6, we now define two objects, a base class and a derived classobject:ComSet com(intercom); NavComSet navcom(intercom2, dial2);
The objectnavcom is constructed using anIntercom and aVHF_Dial object. However, aNavComSet is at the same time aComSet, allowing theassignmentfromnavcom (a derived classobject)tocom (a base class object):
com = navcom;
The effect of this assignment is that the objectcom nowcommunicates withintercom2. As aComSet does not have aVHF_Dial,thenavcom'sdial is ignored by the assignment. When assigning abase class object from a derived class object only the base class data membersare assigned, other data members are dropped, a phenomenon calledslicing. In situations like these slicing probably does not have seriousconsequences, but when passing derived class objects to functions definingbase class parameters or when returning derived class objects from functionsreturning base class objects slicing also occurs and might have unwelcomeside-effects.
The assignment from a base class object to a derived class object isproblematic. In a statement like
navcom = com;
it isn't clear how to reassign theNavComSet'sVHF_Dial datamember as they are missing in theComSet objectcom. Such anassignment is therefore refused by thecompiler. Although derived class objects are also base class objects, thereverse does not hold true: a base class object is not also a derived classobject.
The following general rule applies: in assignments in which base classobjects and derived class objects are involved, assignments in which data aredropped are legal (calledslicing). Assignments in which data remainunspecified arenot allowed. Of course, it is possible to overload anassignment operator to allow the assignment of a derived class object from abase class object. To compile the statement
navcom = com;
the classNavComSet must have defined an overloaded assignmentoperator accepting aComSet object for its argument. In that case it's upto the programmer to decide what the assignment operator will do with themissing data.
Vehicle classes, and define the following objects andpointer variable:Land land(1200, 130); Car car(500, 75, "Daf"); Truck truck(2600, 120, "Mercedes", 6000); Vehicle *vp;
Now we can assign the addresses of the three objects ofthe derived classes to theVehicle pointer:
vp = &land; vp = &car; vp = &truck;
Each of these assignments is acceptable. However, animplicit conversion of thederived class to thebase classVehicle is used, sincevp is defined as a pointer to aVehicle. Hence, when usingvp only the member functions manipulatingmass can be called as this is theVehicle'sonly functionality.As far as the compiler can tell this is the objectvp points to.
The same holds true for references toVehicles. If, e.g., a function is defined having aVehicle referenceparameter, the function may be passed an object of a class derived fromVehicle. Inside the function, the specificVehicle members remainaccessible. This analogy between pointers and references holds true ingeneral. Remember that a reference is nothing but apointer in disguise: itmimics a plain variable, but actually it is a pointer.
This restricted functionality has an important consequencefor the classTruck. Followingvp = &truck,vp points toaTruck object. So,vp->mass() returns 2600 instead of8600 (the combined mass of the cabin and of the trailer: 2600 + 6000),which would have been returned bytruck.mass().
When a function is called using a pointer to an object, then thetype of the pointer (and not the type of the object) determineswhich memberfunctions are available and can be executed. In other words,C++implicitly converts the type of an object reached through a pointer to thepointer's type.
If the actual type of the object pointed to by a pointer is known, anexplicit type cast can be used to access the full set of member functionsthat are available for the object:
Truck truck; Vehicle *vp; vp = &truck; // vp now points to a truck object Truck *trp; trp = static_cast<Truck *>(vp); cout << "Make: " << trp->name() << '\n';
Here, the second to last statement specifically casts aVehicle *variable to aTruck *. As usual (when using casts), this codeis not without risk. Itonly works ifvp really points to aTruck. Otherwise the program may produce unexpected results.
new[] calls the defaultconstructor of a class to initialize theallocated objects. For example, to allocate an array of 10 strings we can donew string[10];
but it is not possible to use another constructor. Assuming that we'd wantto initialize the strings with the texthello world, we can't writesomething like:
new string{ "hello world" }[10];The initialization of a dynamically allocated object usually consists of atwo-step process: first the array is allocated (implicitly calling the defaultconstructor); second the array's elements are initialized, as in the followinglittle example:
string *sp = new string[10]; fill(sp, sp + 10, string{ "hello world" });These approaches all suffer from `double initializations', comparableto not using member initializers in constructors.
One way to avoid double initialization is to use inheritance.Inheritance can profitably be used to call non-default constructors incombination with operatornew[]. The approach capitalizes on thefollowing:
The above also suggests a possible approach:
new[]'s return expression to a pointer to base class objects.hello world:#include <iostream>#include <string>#include <algorithm>#include <iterator>using namespace std;struct Xstr: public string{ Xstr() : string("hello world") {}};int main(){ string *sp = new Xstr[10]; copy(sp, sp + 10, ostream_iterator<string>{ cout, "\n" });} Of course, the above example is fairly unsophisticated, but it's easy topolish the example: the classXstr can be defined inan anonymous namespace, accessible only to a functiongetString() whichmay be given asize_t nObjects parameter, allowing users to specify thenumber ofhello world-initialized strings they would like to allocate.Instead of hard-coding the base class arguments it's also possible to usevariables or functions providing the appropriate values for the base classconstructor's arguments. In the next example alocal classXstr is defined inside a functionnStrings(size_t nObjects, char const *fname), expecting the number ofstring objects to allocate and the name of a file whose subsequent linesare used to initialize the objects. The local classis invisible outside of the functionnStrings, so no special namespacesafeguards are required.
As discussed in section7.9, members of local classes cannot accesslocal variables from their surrounding function. However, they can accessglobal and static data defined by the surrounding function.
Using a local class neatly allows us to hide the implementation detailswithin the functionnStrings, which simply opens the file, allocates theobjects, and closes the file again. Since the local class is derived fromstring, it can use anystring constructor for its base classinitializer. In this particular case it doesn't even do that, as copy elisionensures thatXstr's base classstring in fact is thestringreturned bynextLine. That latter function'sstring subsequentlyreceives the lines of the just opened stream. AsnextLine is a staticmember function, it's available toXstr default constructor's memberinitializers even though at that time theXstr object isn't available yet.
#include <fstream>#include <iostream>#include <string>#include <algorithm>#include <iterator>using namespace std;string *nStrings(size_t size, char const *fname){ static thread_local ifstream in; struct Xstr: public string { Xstr() : string(nextLine()) {} static string nextLine() { string line; getline(in, line); return line; // copy elision turns this } // into Xstr's base class string }; in.open(fname); string *sp = new Xstr[size]; in.close(); return sp;}int main(){ string *sp = nStrings(10, "nstrings.cc"); copy(sp, sp + 10, ostream_iterator<string>{ cout, "\n" });} When this program is run, it displays the first 10 lines of the filenstrings.cc.Note that the example defines astatic thread_local ifstreamobject. Thread_local variables are formally introduced in chapter20. Thethread_local specification assures that the functioncan safely be used, even in multithreaded programs.
A completely different way to avoid the double initialization (not usinginheritance) is to use placement new (cf. section9.1.5): simplyallocate the required amount of memory followed by the proper in-placeallocation of the objects, using the appropriate constructors. In the nextexample a pair of staticconstruct/destroy members are used to perform therequired initialization. In the exampleconstruct expects anistreamthat provides the initialization strings for objects of a classStringsimply containing astd::string object.Construct first allocatesenough memory for thenString objects plus room for an initialsize_t value. This initialsize_t value is then initialized withn. Next, in afor statement, lines are read from the provided streamand the lines are passed to the constructors, using placement newcalls. Finally the address of the firstString object is returned. Then,the destruction of the objects is handled by the memberdestroy. Itretrieves the number of objects to destroy from thesize_t it finds justbefore the location of the address of the first object to destroy. The objectsare then destroyed by explicitly calling their destructors. Finally the rawmemory, originally allocated byconstruct is returned.
#include <fstream>#include <iostream>#include <string>using namespace std;class String{ union Ptrs { void *vp; String *sp; size_t *np; }; std::string d_str; public: String(std::string const &txt) : d_str(txt) {} ~String() { cout << "destructor: " << d_str << '\n'; } static String *construct(istream &in, size_t n) { Ptrs p = {operator new(n * sizeof(String) + sizeof(size_t))}; *p.np++ = n; string line; for (size_t idx = 0; idx != n; ++idx) { getline(in, line); new(p.sp + idx) String{ line }; } return p.sp; } static void destroy(String *sp) { Ptrs p = {sp}; --p.np; for (size_t n = *p.np; n--; ) sp++->~String(); operator delete(p.vp); }};int main(){ String *sp = String::construct(cin, 5); String::destroy(sp);}/* After providing 5 lines containing, respectively alpha, bravo, charley, delta, echo the program displays: destructor: alpha destructor: bravo destructor: charley destructor: delta destructor: echo*/