Vehicle *vp, points to aCar objectCar'sspeed orbrandName members can't be used.In the previous chapter two fundamental ways classes may be related to eachother were discussed: a class may beimplemented-in-terms-of anotherclass and it can be stated that a derived classis-a base class. Theformer relationship is usually implemented using composition, the latteris usually implemented using a special form of inheritance, calledpolymorphism, the topic of this chapter.
Anis-a relationship between classes allows us to apply theLiskov Substitution Principle (LSP) according to which a derivedclass object may be passed to and used by code expecting a pointer orreference to a base class object. In theC++ Annotations so far the LSP has beenapplied many times. Every time anostringstream, ofstream orfstreamwas passed to functions expecting anostream we've been applying thisprinciple. In this chapter we'll discover how to design our own classesaccordingly.
LSP is implemented using a technique calledpolymorphism: although a baseclass pointer is used it performs actions defined in the (derived) classof the object it actually points to. So, aVehicle *vp might behave likeaCar * when pointing to aCar (In one of the StarTrekmovies, Capt. Kirk was in trouble, as usual. He met an extremely beautifullady who, however, later on changed into a hideous troll. Kirk was quitesurprised, but the lady told him: ``Didn't you know I am a polymorph?'').
Polymorphism is implemented using a feature calledlate binding. It'scalled that way because the decisionwhich function to call (a base classfunction or a function of a derived class) cannot be made atcompile-time,but is postponed until the program is actually executed: only then it isdetermined which member function will actually be called.
InC++ late binding isnot the default way functions are called. Bydefaultstatic binding (orearly binding) is used. With staticbinding the functions that are called are determined by the compiler, merelyusing the class types of objects, object pointers or object references.
Late binding is an inherently different (and slightly slower) process as it isdecided atrun-time, rather than atcompile-time what function is goingto be called. AsC++ supportsboth late- and early-bindingC++programmers are offered an option as to what kind of binding to use. Choicescan be optimized to the situations at hand. Many other languages offeringobject oriented facilities (e.g.,Java) only or by default offer latebinding.C++ programmers should be keenly aware of this. Expecting earlybinding and getting late binding may easily produce nasty bugs.
Let's look at a simple example to start appreciating the differences betweenlate and early binding. The example merely illustrates. Explanations ofwhy things are as shown are shortly provided.
Consider the following little program:
#include <iostream> using namespace std; class Base { protected: void hello() { cout << "base hello\n"; } public: void process() { hello(); } }; class Derived: public Base { protected: void hello() { cout << "derived hello\n"; } }; int main() { Derived derived; derived.process(); } The important characteristic of the above program is theBase::processfunction, callinghello. Asprocess is the only member that is definedin the public interface it is the only member that can be called by code notbelonging to the two classes. The classDerived, derived fromBaseclearly inheritsBase's interface and soprocess is also available inDerived. So theDerived object inmain is able to callprocess, but nothello.So far, so good. Nothing new, all this was covered in the previouschapter. One may wonder whyDerived was defined at all. It waspresumably defined to create an implementation ofhello that's appropriateforDerived but differing fromBase::hello'simplementation.Derived's author's reasoning was as follows:Base'simplementation ofhello is not appropriate; aDerived class object canremedy that by providing an appropriate implementation. Furthermore our authorreasoned:
``since the type of anobject determines the interface that is used,processmust callDerived::helloashellois called viaprocessfrom aDerivedclass object''.
Unfortunately our author's reasoning is flawed, due to static binding. WhenBase::process was compiled static binding caused the compiler to bindthehello call toBase::hello().
The authorintended to create aDerived class thatis-aBaseclass. That only partially succeeded:Base's interface was inherited, butafter thatDerived has relinquished all control over what happens. Oncewe're inprocess we're only able to seeBase's memberimplementations. Polymorphism offers a way out, allowing us to redefine (in aderived class) members of a base class allowing these redefined members to beused from the base class's interface.
This is the essence of LSP: public inheritance should not be used to reuse thebase class members (in derived classes) but to be reused (by the base class,polymorphically using derived class members reimplementing base classmembers).
Take a second to appreciate the implications of the above little program. Thehello andprocess members aren't too impressive, but the implicationsof the example are. Theprocess member could implement directory travel,hello could define the action to perform when encountering afile.Base::hello might simply show the name of a file, butDerived::hello might delete the file; might only list its name if itsyounger than a certain age; might list its name if it contains a certain text;etc., etc.. Up to nowDerived would have to implementprocess'sactions itself; Up to now code expecting aBase class reference or pointercould only performBase's actions. Polymorphism allows us to reimplementmembers of base classes and to use those reimplemented members in codeexpecting base class references or pointers. Using polymorphism existing codemay be reused by derived classes reimplementing the appropriate members oftheir base classes. It's about time to uncover how this magic can be realized.
Polymorphism, which is not the default inC++, solves the problem andallows the author of the classes to reach its goal. For the curious reader:prefixvoid hello() in theBase class with the keywordvirtual andrecompile. Running the modified program produces the intended and expectedderived hello. Why this happens is explained next.
Vehicle * activatesVehicle's memberfunctions, even when pointing to an object of a derived class. This is knownas asearly orstatic binding: the function tocall is determined atcompile-time. InC++late ordynamic binding is realized usingvirtual member functions.A member function becomes avirtual member function when its declarationstarts with the keywordvirtual. It is stressed once again that inC++, different from several other object oriented languages, this isnot the default situation. By defaultstatic binding is used.
Once a function is declaredvirtual in a base class, it remains virtual inall derived classes. The keywordvirtual should not be mentioned formembers in derived classes which are declared virtual in base classes. Inderived classes those members should be provided with theoverrideindicator, allowing the compiler to verify that you're indeed referring to anexisting virtual member function.
In the vehicle classification system (see section13.1), let'sconcentrate on the membersmass andsetMass. These members define theuser interface of the classVehicle. What we would like to accomplishis that this user interface can be used forVehicle and for any classinheriting fromVehicle, since objects of those classes are themselvesalsoVehicles.
If we can define the user interface of our base class (e.g.,Vehicle) suchthat it remains usable irrespective of the classes we derive fromVehicleour software achieves an enormous reusability: we design our software aroundVehicle's user interface, and our software will also properly function forderived classes. Using plain inheritance doesn't accomplish this. If we define
std::ostream &operator<<(std::ostream &out, Vehicle const &vehicle) { return out << "Vehicle's mass is " << vehicle.mass() << " kg."; }andVehicle's membermass returns 0, butCar's membermass returns 1000, then twice a mass of 0 is reported when the followingprogram is executed:
int main() { Vehicle vehicle; Car vw{ 1000, 160, "Golf" }; cout << vehicle << '\n' << vw << '\n'; }We've defined an overloaded insertion operator, but since it only knowsaboutVehicle's user interface, `cout << vw' will usevw'sVehicle's user interface as well, thus displaying a mass of 0.
Reusability is enhanced if we add aredefinable interface to the baseclass's interface. A redefinable interface allows derived classes to fill intheir own implementation, without affecting the user interface. At the sametime the user interface will behave according to the derived class's wishes,and not just to the base class's default implementation.
Members of the reusable interface should be declared in the class'sprivate sections: conceptually they merely belong to their own classes(cf. section14.7). In the base class these members should bedeclaredvirtual. These members can be redefined (overridden) by derivedclasses, and should there be provided withoverride indicators.
We keep our user interface (mass), and add the redefinable membervmass toVehicle's interface:
class Vehicle { public: size_t mass() const; size_t si_mass() const; // see below private: virtual size_t vmass() const; };Separating the user interface from the redefinable interface is a sensiblething to do. It allows us to fine-tune the user interface (only one point ofmaintenance), while at the same time allowing us to standardize the expectedbehavior of the members of the redefinable interface. E.g., in many countriesthe International system of units is used, using the kilogram as the unit formass. Some countries use other units (like thelbs: 1 kg beingapprox. 2.2046 lbs). By separating the user interface from the redefinableinterface we can use one standard for the redefinable interface, and keep theflexibility of transforming the informationad-lib in the userinterface.
Just to maintain a clean separation of user- and redefinable interface wemight consider adding another accessor toVehicle, providing thesi_mass, simply implemented like this:
size_t Vehicle::si_mass() const { return vmass(); }IfVehicle supports a memberd_massFactor then itsmass member canbe implemented like this:
size_t Vehicle::mass() { return d_massFactor * si_mass(); }Vehicle itself could definevmass so that it returns a tokenvalue. E.g.,
size_t Vehicle::vmass() { return 0; }Now let's have a look at the classCar. It is derived fromVehicle, and it inheritsVehicle's user interface. It also has a datamembersize_t d_mass, and it implements its own reusable interface:
class Car: public Vehicle { ... private: size_t vmass() override; }IfCar constructors require us to specify the car's mass (storedind_mass), thenCar simply implements itsvmass member likethis:
size_t Car::vmass() const { return d_mass; }The classTruck, inheriting fromCar needs two mass values: thetractor's mass and the trailer's mass. The tractor's mass is passed to itsCar base class, the trailor's mass is passed to itsVehicle d_trailordata member.Truck, too, overridesvmass, this time returning the sumof its tractor and trailor masses:
size_t Truck::vmass() const { return Car::si_mass() + d_trailer.si_mass(); }Once a class member has been declaredvirtual it becomesa virtual member in all derived classes, whether or not these members areprovided with theoverride indicator. Butoverrideshould be used,as it allows to compiler to catch typos when writing down the derived classinterface.
A member function may be declaredvirtualanywhere in aclass hierarchy, but this probably defeats the underlying polymorphicclass design, as the original base class is no longer capable of completelycovering the redefinable interfaces of derived classes. If, e.g,mass isdeclared virtual inCar, but not inVehicle, then the specificcharacteristics of virtual member functions would only be available forCar objects and for objects of classes derived fromCar. For aVehicle pointer or reference static binding would remain to be used.
The effect of late binding (polymorphism) is illustrated below:
void showInfo(Vehicle &vehicle) { cout << "Info: " << vehicle << '\n'; } int main() { Car car(1200); // car with mass 1200 Truck truck(6000, 115, // truck with cabin mass 6000, "Scania", 15000); // speed 115, make Scania, // trailer mass 15000 showInfo(car); // see (1) below showInfo(truck); // see (2) below Vehicle *vp = &truck; cout << vp->speed() << '\n';// see (3) below }Now thatmass is definedvirtual, late binding is used:
Car's mass is displayed;Truck's mass is displayed;speed is not a member ofVehicle, and hence not callable via aVehicle*.virtual characteristiconly influences the type of binding (early vs. late), not the set of memberfunctions that is visible to the pointer.Through virtual members derived classes may redefine the behavior performed byfunctions called from base class members or from pointers or references tobase class objects. This redefinition of base class members by derived classesis calledoverriding members.
Vehicle woulddefine these members: public: void Vehicle::prepare() { vPrepare(); } private: virtual void Vehicle::vPrepare() { cout << "Preparing the Vehicle\n"; } andCar would overridevPrepare: virtual void Car::vPrepare() { cout << "Preparing the Car\n"; } thenPreparing the Car would be shown by the following code fragment:Car car{1200}; Vehicle &veh = car; veh.prepare();Maybe a preparation is always required. So why not do it in the baseclass's constructor? Thus, theVehicle's constructor could be defined as:
Vehicle::Vehicle() { prepare(); }However, the following code fragment showsPreparing the Vehicle,andnotPreparing the Car:Car car{1200};As base classes' constructors do not recognize overridden virtual membersVehicle's constructor simply calls its ownvPrepare member instead ofVehicle::vPrepare.There is clear logic to base class constructors not recognizing overriddenmember functions: polymorphism allows us to tailor the base class's interfaceto derived classes. Virtual members exist to realize this tailoring process.But that's completely different from not being able to call derived classes'members from base classes' constructors: at that point the derived classobjects haven't yet properly been initialized. When derived class objects areconstructed their base class parts are constructed before the derived classobjects themselves are in a valid state. Therefore,if a base classconstructor would be allowed to call an overridden virtual member then thatmember would most likely use data of the derived class, which at that pointhaven't properly been initialized yet (often resulting in undefined behaviorlike segmentation faults).
Vehicle *vp = new Land{ 1000, 120 }; delete vp; // object destroyedHeredelete is applied to a base class pointer. As the base classdefines the available interfacedelete vp calls~Vehicle and~Landremains out of sight. Assuming thatLand allocates memory amemory leak results. Freeing memory is not the only action destructors canperform. In general they may perform any action that's necessary when anobject ceases to exist. But here none of the actions defined by~Land areperformed. Bad news....
InC++ this problem is solved byvirtual destructors. Adestructor can be declaredvirtual. When a base class destructor isdeclared virtual then the destructor of the actual class pointed to by a baseclass pointerbp is going to be called whendelete bp isexecuted. Thus, late binding is realized for destructors even though thedestructors of derived classes have unique names. Example:
class Vehicle { public: virtual ~Vehicle(); // all derived class destructors are // now virtual as well. };By declaring a virtual destructor, the abovedelete operation(delete vp) correctly callsLand's destructor, rather thanVehicle's destructor.
Once a destructor is called it performs as usual, whether or not itis a virtual destructor. So,~Land first executes its own statementsand then calls~Vehicle. Thus, the abovedelete vp statementuses late binding to call~Vehicle and from this point on the objectdestruction proceeds as usual.
Destructors should always be definedvirtual in classes designed as abase class from which other classes are going to be derived. Often thosedestructors themselves have no tasks to perform. In these cases the virtual destructor is given an empty body. For example, the definition ofVehicle::~Vehicle() may be as simple as:
Vehicle::~Vehicle() {}Resist the temptation to define virtual destructors (even emptydestructors)inline as this complicates classmaintenance. Section14.11 discusses the reason behind thisrule of thumb.
Vehicle is provided with its own concrete implementationsof its virtual members (mass andsetMass). However, virtual memberfunctions do not necessarilyhave to be implemented in base classes.When the implementations of virtual members are omitted from base classes theclass imposes requirements upon derived classes. The derived classes arerequired to provide the `missing implementations'.
This approach, in some languages (likeC#, Delphi andJava) known as aninterface, defines aprotocol. Derived classesmust obey the protocol by implementing theas yet not implemented members. If a class contains at least one member whoseimplementation is missing no objects of that class can be defined.
Such incompletely defined classes are always base classes. They enforce aprotocol by merely declaring names, return values and arguments of some oftheir members. These classes are callabstract classes orabstract base classes. Derived classes becomenon-abstract classes by implementing the as yet not implemented members.
Abstract base classes are the foundation of manydesign patterns (cf.Gamma et al. (1995)), allowing the programmer to create highlyreusable software. Some of these design patterns are covered by theC++ Annotations (e.g, theTemplate Method in section26.2), but for athorough discussion of design patterns the reader is referred to Gammaetal.'s book.
Members that are merely declared in base classes are calledpure virtual functions. A virtual member becomes a pure virtual memberby postfixing= 0 to its declaration (i.e., by replacing the semicolonending its declaration by `= 0;'). Example:
#include <iosfwd> class Base { public: virtual ~Base(); virtual std::ostream &insertInto(std::ostream &out) const = 0; }; inline std::ostream &operator<<(std::ostream &out, Base const &base) { return base.insertInto(out); }All classes derived fromBasemust implement theinsertIntomember function, or their objects cannot be constructed. This is neat: allobjects of class types derived fromBase can now always be inserted intoostream objects.
Could thevirtual destructor of a base class ever be a pure virtualfunction? The answer to this question is no. First of all, there is no need toenforce the availability of destructors in derived classes as destructors areprovided by default (unless a destructor is declared with the= deleteattribute). Second, if it is a pure virtual member its implementation does notexist. However, derived class destructors eventually call their base classdestructors. How could they call base class destructors if theirimplementations are lacking? More about this in the next section.
Often, but not necessarily, pure virtual member functions areconst member functions. This allows theconstruction of constant derived class objects. In other situations this mightnot be necessary (or realistic), andnon-constant member functions might be required. The general rule forconst member functions also applies to pure virtual functions: if themember function alters the object's data members, it cannot be aconstmember function.
Abstract base classes frequently don't have data members. However, once a base classdeclares a pure virtual member itmust be declared identically in derivedclasses. If the implementation of a pure virtual function in a derived classalters the derived class object's data, thenthat function cannot bedeclared as aconst member. Therefore, the author of an abstract baseclass should carefully consider whether a pure virtual member function shouldbe aconst member function or not.
= 0; specification, butimplement it as well. Since the= 0; ends in a semicolon, the pure virtualmember is always at most a declaration in its class, but an implementation mayeither be provided outside from its interface (maybe usinginline).Pure virtual member functions may be called from derived class objects orfrom its class or derived class members by specifying the base class and scoperesolution operator together with the member to be called. Example:
#include <iostream>class Base{ public: virtual ~Base(); virtual void pureimp() = 0;};Base::~Base(){}void Base::pureimp(){ std::cout << "Base::pureimp() called\n";}class Derived: public Base{ public: void pureimp() override;};inline void Derived::pureimp(){ Base::pureimp(); std::cout << "Derived::pureimp() called\n";}int main(){ Derived derived; derived.pureimp(); derived.Base::pureimp(); Derived *dp = &derived; dp->pureimp(); dp->Base::pureimp();}// Output:// Base::pureimp() called// Derived::pureimp() called// Base::pureimp() called// Base::pureimp() called// Derived::pureimp() called// Base::pureimp() calledImplementing a pure virtual member has limited use. One could argue thatthe pure virtual member function's implementation may be used to perform tasksthat can already be performed at the base class level. However, there is noguarantee that the base class virtual member function is actually going to becalled. Therefore base class specific tasks could as well be offered by aseparate member, without blurring the distinction between a member doing somework and a pure virtual member enforcing a protocol.
Value is avalue class. It offers a copy constructor,an overloaded assignment operator, maybe move operations, and a public,non-virtual constructor. In section14.7 it is argued that suchclasses are not suited as base classes. New classes should not inherit fromValue. How to enforce this?Base defines a virtual memberv_process(int32_t). A class derived fromBase needs to override thismember, but the author mistakingly definedv_proces(int32_t). How toprevent such errors, breaking the polymorphic behavior of the derived class?Derived, derived from a polymorphicBase classoverrides the memberBase::v_process, but classes that are in turn derivedfromDerived should no longer overridev_process, butmay overrideother virtual members likev_call andv_display. How to enforce thisrestricted polymorphic character for classes derived fromDerived?final andoverride are used to realizethe above. These identifiers are special in the sense that they only requiretheir special meanings in specific contexts. Outside of this context they arejust plain identifiers, allowing the programmer to define a variable likebool final.The identifierfinal can be applied to class declarations to indicatethat the class cannot be used as a base class. E.g.:
class Base1 final // cannot be a base class {}; class Derived1: public Base1 // ERR: Base1 is final {}; class Base2 // OK as base class {}; class Derived2 final: public Base2 // OK, but Derived2 can't be {}; // used as a base class class Derived: public Derived2 // ERR: Derived2 is final {};The identifierfinal can also be added to virtual memberdeclarations. This indicates that those virtual members cannot be overriddenby derived classes. The restricted polymorphic character of a class, mentionedabove, can thus be realized as follows:
class Base { virtual int v_process(); // define polymorphic behavior virtual int v_call(); virtual int v_display(); }; class Derived: public Base // Derived restricts polymorphism { // to v_call and v_display virtual int v_process() final; }; class Derived2: public Derived { // int v_process(); No go: Derived:v_process is final virtual int v_display(); // OK to override };To allow the compiler to detect typos, differences in parameter types, ordifferences in member function modifiers (e.g.,const vs. non-const)the identifieroverride can (should) be appended to derived class membersoverriding base class members. E.g.,
class Base { virtual int v_process(); virtual int v_call() const; virtual int v_display(std::ostream &out); }; class Derived: public Base { virtual int v_proces() override; // ERR: v_proces != v_process virtual int v_call() override; // ERR: not const // ERR: parameter types differ virtual int v_display(std::istream &out) override; };fstream, one classoffering features ofifstream andofstream. In chapter13 we learned that a class may be derived from multiple baseclasses. Such a derived class inherits the properties of all its baseclasses. Polymorphism can also be used in combination with multipleinheritance.Consider what would happen if more than one `path' leads from the derivedclass up to its (base) classes. This is illustrated in the next (fictitious)example where a classDerived is doubly derived fromBase:
class Base { int d_field; public: void setfield(int val); int field() const; }; inline void Base::setfield(int val) { d_field = val; } inline int Base::field() const { return d_field; } class Derived: public Base, public Base {};Due to the double derivation,Base's functionality now occurs twice inDerived. This results inambiguity: when the functionsetfield() iscalled for aDerived class object, which function will that be as thereare two of them? The scope resolution operator won't come to the rescue and sotheC++ compiler cannot compile the above example and (correctly)identifies an error.
The above code clearly duplicates its base class in the derivation, which canof course easily be avoided by not doubly deriving fromBase (or by usingcomposition (!)). But duplication of a base class can also occur throughnested inheritance, where an object is derived from, e.g., aCar andfrom anAir (cf. section13.1). Such a class would be neededto represent, e.g., a flying car (such as the one in James Bondvs. the Man with the Golden Gun...). AnAirCar would ultimately containtwoVehicles, and hence twomass fields, twosetMass()functions and twomass() functions. Is this what we want?
AirCar introducesambiguity, whenderived fromCar andAir.AirCar is aCar, hence aLand, and hence aVehicle.AirCar is also anAir, and hence aVehicle.Vehicle data is further illustrated inFigure16.
AirCar is shown inFigure17
AirCar object.
AirCar object,and will therefore not compile statements like:AirCar jBond; cout << jBond.mass() << '\n';
Which member functionmass to call cannot be determined by thecompiler but the programmer has two possibilities to resolve the ambiguity forthe compiler:
// let's hope that the mass is kept in the Car// part of the object..cout << jBond.Car::mass() << '\n';
The scope resolution operator and the class name are put right beforethe name of the member function.
mass could be created forthe classAirCar:int AirCar::mass() const{ return Car::mass();}AirCar to take special precautions.However, there exists a more elegant solution, discussed in the nextsection.
AirCar representstwoVehicles. This not only results in anambiguity about whichfunction to use to access themass data, but it also defines twomass fields in anAirCar. This is slightly redundant, since we canassume that anAirCar has but one mass.It is, however, possible to define anAirCar as a class consisting ofbut oneVehicle and yet using multiple derivation. This is realized bydefining the base classes that are multiply mentioned in a derived class'sinheritance tree as avirtual base class.
For the classAirCar this implies a small change when deriving anAirCar fromLand andAir classes:
class Land: virtual public Vehicle { // etc }; class Car: public Land { // etc }; class Air: virtual public Vehicle { // etc }; class AirCar: public Car, public Air { };Virtual derivation ensures that aVehicle isonly added once to a derived class. This means that the route along which aVehicle is added to anAirCar is no longer depending on its directbase classes; we can only state that anAirCar is aVehicle. Theinternal organization of anAirCar after virtual derivation is shown inFigure18.

AirCar object when the base classes are virtual.
When a classThird inherits from a base classSecond which in turninherits from a base classFirst then theFirst class constructorcalled by theSecond class constructor is also used when thisSecondconstructor is used when constructing aThird object. Example:
class First { public: First(int x); }; class Second: public First { public: Second(int x) : First(x) {} }; class Third: public Second { public: Third(int x) : Second(x) // calls First(x) {} };The above no longer holds true whenSecond uses virtual derivation.WhenSecond uses virtual derivation its base class constructor isignored whenSecond's constructor is called fromThird. InsteadSecond by default callsFirst's default constructor. This isillustrated by the next example:
class First { public: First() { cout << "First()\n"; } First(int x); }; class Second: public virtual First // note: virtual { public: Second(int x) : First(x) {} }; class Third: public Second { public: Third(int x) : Second(x) {} }; int main() { Third third{ 3 }; // displays `First()' } When constructingThirdFirst's default constructor is used bydefault.Third's constructor, however, may overrule this default behaviorby explicitly specifying the constructor to use. Since theFirst objectmust be available beforeSecond can be constructed it must be specifiedfirst. To callFirst(int) when constructingThird(int) the latterconstructor can be defined as follows: class Third: public Second { public: Third(int x) : First(x), // now First(int) is called. Second(x) {} };This behavior may seem puzzling when simple linear inheritance is used butit makes sense when multiple inheritance is used with base classes usingvirtual inheritance. ConsiderAirCar: whenAir andCar bothvirtually inherit fromVehicle willAir andCar both initializethe commonVehicle object? If so, which one is going to be called first?What ifAir andCar use differentVehicle constructors? All thesequestions can be avoided by passing the responsibility for the initializationof a common base class to the class eventually using the common base classobject. In the above exampleThird. HenceThird is provided anopportunity to specify the constructor to use when initializingFirst.
Multiple inheritance may also be used to inherit from classes that do not alluse virtual inheritance. Assume we have two classes,Derived1 andDerived2, both (possibly virtually) derived fromBase.
We now address the question which constructors will be called when calling aconstructor of the classFinal: public Derived1, public Derived2.
To distinguish the involved constructorsBase1 indicates theBaseclass constructor called as base class initializer forDerived1 (andanalogously:Base2 called fromDerived2). A plainBase indicatesBase's default constructor.
Derived1 andDerived2 indicate the base class initializers used whenconstructing aFinal object.
Now we're ready to distinguish the various cases when constructing an objectof the classFinal: public Derived1, public Derived2:
Derived1: public BaseDerived2: public Base
This is normal, non virtual multiple derivation. The following constructors are called in the order shown:Base1,Derived1,Base2,Derived2
Derived1: public BaseDerived2: virtual public Base
OnlyDerived2uses virtual derivation.Derived2's base class constructor is ignored. Instead,Baseis called and it is called prior to any other constructor:Base,Base1,Derived1,Derived2As only one class uses virtual derivation,two
Baseclass objects remain available in the eventualFinalclass.
Derived1: virtual public BaseDerived2: public Base
OnlyDerived1uses virtual derivation.Derived1's base class constructor is ignored. Instead,Baseis called and it is called prior to any other constructor. Different from the first (non-virtual) caseBaseis now called, rather thanBase1:Base,Derived1,Base2,Derived2
Derived1: virtual public BaseDerived2: virtual public Base
Both base classes use virtual derivation and so onlyoneBaseclass object will be present in theFinalclass object. The following constructors are called in the order shown:Base,Derived1,Derived2
Truck (cf.section13.5): class Truck: public Car { int d_trailer_mass; public: Truck(); Truck(int engine_mass, int sp, char const *nm, int trailer_mass); void setMass(int engine_mass, int trailer_mass); int mass() const; }; Truck::Truck(int engine_mass, int sp, char const *nm, int trailer_mass) : Car(engine_mass, sp, nm) { d_trailer_mass = trailer_mass; } int Truck::mass() const { return // sum of: Car::mass() + // engine part plus trailer_mass; // the trailer }This definition shows how aTruck object is constructed to contain twomass fields: one via its derivation fromCar and one via its ownintd_trailer_mass data member. Such a definition is of course valid, but itcould also be rewritten. We could derive aTruck from aCarand from aVehicle, thereby explicitly requesting the double presenceof aVehicle; one for the mass of the engine and cabin, and one for themass of the trailer. A slight complication is that a class organization like
class Truck: public Car, public Vehicle
is not accepted by theC++ compiler. As aVehicle is already partof aCar, it is therefore not needed once again. This organization may,however, be accepted using a small trick. By creating an additional classinheriting fromVehicle and derivingTruck from that additional classrather than directly fromVehicle the problem is solved. Simply derive aclassTrailerVeh fromVehicle, and thenTruck fromCar andTrailerVeh:
class TrailerVeh: public Vehicle { public: TrailerVeh(int mass) : Vehicle(mass) {} }; class Truck: public Car, public TrailerVeh { public: Truck(); Truck(int engine_mass, int sp, char const *nm, int trailer_mass); void setMass(int engine_mass, int trailer_mass); int mass() const; }; inline Truck::Truck(int engine_mass, int sp, char const *nm, int trailer_mass) : Car(engine_mass, sp, nm), TrailerVeh(trailer_mass) {} inline int Truck::mass() const { return // sum of: Car::mass() + // engine part plus TrailerVeh::mass(); // the trailer }typeid operators.dynamic_cast is used to convert a baseclass pointer or reference to aderived class pointer or reference. This is also known asdown-casting.typeid operator returns the actual type of an expression.dynamic_cast<> operator is used to convert a baseclass pointer or reference to,respectively, aderived class pointer or reference. This is also calleddown-casting as direction of the cast isdown the inheritance tree.A dynamic cast's actions are determinedrun-time; it can only be used ifthe base class declares at least one virtual member function. For the dynamiccast to succeed, the destination class'sVtable must be equal to theVtable to which the dynamic cast's argument refers to, lest the cast failsand returns 0 (if a dynamic cast of a pointer was requested) or throws astd::bad_cast exception (if a dynamic cast of a reference was requested).
In the following example a pointer to the classDerived is obtained fromtheBase class pointerbp:
class Base { public: virtual ~Base(); }; class Derived: public Base { public: char const *toString(); }; inline char const *Derived::toString() { return "Derived object"; } int main() { Base *bp; Derived *dp, Derived d; bp = &d; dp = dynamic_cast<Derived *>(bp); if (dp) cout << dp->toString() << '\n'; else cout << "dynamic cast conversion failed\n"; }In the condition of the aboveif statement the success of the dynamiccast is verified. This verification is performed atrun-time, as theactual class of the objects to which the pointer points is only known by then.
If a base class pointer is provided, the dynamic cast operator returns 0 onfailure and a pointer to the requested derived class on success.
Assume avector<Base *> is used. The pointers of such a vector maypoint to objects of various classes, all derived fromBase. A dynamic castreturns a pointer to the specified class if the base class pointer indeedpoints to an object of the specified class and returns 0 otherwise.
We could determine the actual class of an object a pointer points to byperforming a series of checks to find the derived class to which a base classpointer points. Example:
class Base { public: virtual ~Base(); }; class Derived1: public Base; class Derived2: public Base; int main() { vector<Base *> vb(initializeBase()); Base *bp = vb.front(); if (dynamic_cast<Derived1 *>(bp)) cout << "bp points to a Derived1 class object\n"; else if (dynamic_cast<Derived2 *>(bp)) cout << "bp points to a Derived2 class object\n"; }Alternatively, a reference to a base class object may be available. Inthis case thedynamic_cast operator throws anexception if the downcasting fails. Example:
#include <iostream> #include <typeinfo> class Base { public: virtual ~Base(); virtual char const *toString(); }; inline char const *Base::toString() { return "Base::toString() called"; } class Derived1: public Base {}; class Derived2: public Base {}; Base::~Base() {} void process(Base &b) { try { std::cout << dynamic_cast<Derived1 &>(b).toString() << '\n'; } catch (std::bad_cast) {} try { std::cout << dynamic_cast<Derived2 &>(b).toString() << '\n'; } catch (std::bad_cast) { std::cout << "Bad cast to Derived2\n"; } } int main() { Derived1 d; process(d); } /* Generated output: Base::toString() called Bad cast to Derived2 */ In this example the valuestd::bad_cast is used. Astd::bad_cast exception is thrown if the dynamic cast of a reference to aderived class object fails.Note the form of thecatch clause:bad_cast is the name of a type.Section17.4.1 describes how such a type can be defined.
The dynamic cast operator is a useful tool when an existing base class cannotor should not be modified (e.g., when the sources are not available), and aderived class may be modified instead. Code receiving a base class pointer orreference may then perform a dynamic cast to the derived class to access thederived class's functionality.
You may wonder in what way the behavior of thedynamic_cast differs fromthat of thestatic_cast.
When thestatic_cast is used, we tell the compiler that it must convert apointer or reference to its expression type to a pointer or reference of itsdestination type. This holds true whether the base class declares virtualmembers or not. Consequently, all thestatic_cast's actions can bedetermined by the compiler, and the following compiles fine:
class Base { // maybe or not virtual members }; class Derived1: public Base {}; class Derived2: public Base {}; int main() { Derived1 derived1; Base *bp = &derived1; Derived1 &d1ref = static_cast<Derived1 &>(*bp); Derived2 &d2ref = static_cast<Derived2 &>(*bp); }Pay attention to the secondstatic_cast: here theBase classobject is cast to aDerived2 class reference. The compiler has no problemswith this, asBase andDerived2 are related by inheritance.
Semantically, however, it makes no sense asbp in fact points to aDerived1 class object. This is detected by adynamic_cast. Adynamic_cast, like thestatic_cast, converts related pointer orreference types, but thedynamic_cast provides a run-time safeguard. Thedynamic cast fails when the requested type doesn't match the actual type ofthe object we're pointing at. In addition, thedynamic_cast's use is muchmore restricted than thestatic_cast's use, as thedynamic_cast canonly be used for downcasting to derived classes having virtual members.
In the end a dynamic cast is a cast, and casts should be avoided wheneverpossible. When the need for dynamic casting arises ask yourself whether thebase class has correctly been designed. In situations where code expects abase class reference or pointer the base class interface should be all that isrequired and using a dynamic cast should not be necessary. Maybe the baseclass's virtual interface can be modified so as to prevent the use of dynamiccasts. Start frowning when encountering code using dynamic casts. When usingdynamic casts in your own code always properly document why the dynamic castwas appropriately used and was not avoided.
dynamic_cast operator,typeid is usually applied toreferences to base class objects that refer to derived classobjects.Typeid should only be used with base classes offering virtualmembers.Before usingtypeid the<typeinfo> header file must be included.
Thetypeid operator returns an object of typetype_info.Different compilers may offer different implementations of the classtype_info, but at the very leasttypeid must offer the followinginterface:
class type_info { public: virtual ~type_info(); int operator==(type_info const &other) const; int operator!=(type_info const &other) const; bool before(type_info const &rhs) const; char const *name() const; private: type_info(type_info const &other); type_info &operator=(type_info const &other); };Note that this class has a private copy constructor and a privateoverloaded assignment operator. This prevents code from constructingtype_info objects and prevents code from assigningtype_info objectsto each other. Instead,type_info objects areconstructed and returned by thetypeid operator.
If thetypeid operator is passed a base class reference it is able toreturn the actual name of the type the reference refers to. Example:
class Base; class Derived: public Base; Derived d; Base &br = d; cout << typeid(br).name() << '\n';
In this example thetypeid operator is given a base class reference.It prints the text ``Derived'', being theclass name of the classbr actually refers to. IfBase does not contain virtual functions, thetext ``Base'' is printed.
Thetypeid operator can be used to determine the name of the actualtype of expressions, not just of class typeobjects. For example:
cout << typeid(12).name() << '\n'; // prints: int cout << typeid(12.23).name() << '\n'; // prints: double
Note, however, that the above example is suggestive at most. Itmayprintint anddouble, but this is not necessarily the case. Ifportability is required, make sure no tests against these static, built-intext-strings are required. Check out what your compiler produces in case ofdoubt.
In situations where thetypeid operator is applied todetermine the type of a derived class, a base classreference should be used as the argument of thetypeid operator. Considerthe following example:
class Base; // contains at least one virtual function class Derived: public Base; Base *bp = new Derived; // base class pointer to derived object if (typeid(bp) == typeid(Derived *)) // 1: false ... if (typeid(bp) == typeid(Base *)) // 2: true ... if (typeid(bp) == typeid(Derived)) // 3: false ... if (typeid(bp) == typeid(Base)) // 4: false ... if (typeid(*bp) == typeid(Derived)) // 5: true ... if (typeid(*bp) == typeid(Base)) // 6: false ... Base &br = *bp; if (typeid(br) == typeid(Derived)) // 7: true ... if (typeid(br) == typeid(Base)) // 8: false ...
Here,(1) returnsfalse as aBase * is not aDerived*.(2) returnstrue, as the two pointer types are the same,(3)and(4) returnfalse as pointers to objects are not the objectsthemselves.
On the other hand, if*bp is used in the above expressions, then(1) and(2) returnfalse as an object (or reference to an object)is not a pointer to an object, whereas(5) now returnstrue:*bpactually refers to aDerived class object, andtypeid(*bp) returnstypeid(Derived). A similar result is obtained if a base class referenceis used:7 returningtrue and8 returningfalse.
Thetype_info::before(type_info const &rhs) member is used todetermine thecollating order of classes. This is useful when comparingtwotypes for equality. The function returns a nonzero value if*thisprecedesrhs in the hierarchy or collating order of the used types. When aderived class is compared to its base class the comparison returns 0,otherwise a non-zero value. E.g.:
cout << typeid(ifstream).before(typeid(istream)) << '\n' << // 0 typeid(istream).before(typeid(ifstream)) << '\n'; // not 0
With built-in types the implementor may implement that non-0 is returnedwhen a `wider' type is compared to a `smaller' type and 0 otherwise:
cout << typeid(double).before(typeid(int)) << '\n' << // not 0 typeid(int).before(typeid(double)) << '\n'; // 0
When two equal types are compared, 0 is returned:
cout << typeid(ifstream).before(typeid(ifstream)) << '\n'; // 0
When a0-pointer is passed to theoperator typeid abad_typeidexception is thrown.
We've seen that polymorphic classes on the one hand offer interface membersdefining the functionality that can be requested of base classes and on theother hand offer virtual members that can be overridden. One of the signs ofgood class design is that member functions are designed according to theprinciple of `one function, one task'. In the current context: a class membershould either be a member of the class's public or protected interface or itshould be available as a virtual member for reimplementation by derivedclasses. Often this boils down to virtual members that are defined in the baseclass'sprivate section. Those functions shouldn't be called by code usingthe base class, but they exist to be overridden by derived classes usingpolymorphism to redefine the base class's behavior.
The underlying principle was mentioned before in the introductory paragraphof this chapter: according to theLiskov Substitution Principle(LSP) anis-a relationship between classes (indicating that aderived class objectis a base class object) implies that a derived classobject may be used in code expecting a base class object.
In this case inheritance is usednot to let the derived class use thefacilities already implemented by the base class but to reuse the base classpolymorphically by reimplementing the base class's virtual members in thederived class.
In this section we'll discuss the reasons for using inheritance. Why shouldinheritance (not) be used? If it is used what do we try to accomplish by it?
Inheritance often competes with composition. Consider the following twoalternative class designs:
class Derived: public Base { ... }; class Composed { Base d_base; ... };Why and when preferDerived overComposed and vice versa? Whatkind of inheritance should be used when designing the classDerived?
Composed andDerived are offered as alternatives we are looking at the design of a class (Derived orComposed) thatis-implemented-in-terms-of another class.Composed does itself not makeBase's interface available,Derived shouldn't do so either. The underlying principle is thatprivate inheritance should be used when deriving a classsDerived fromBase whereDerived is-implemented-in-terms-ofBase.std::string members) which can not be realized using inheritance.Base offers members in itsprotected interface that must be used when implementingDerived inheritance must also be used. Again: since we're implementing-in-terms-of the inheritance type should beprivate.D) itself is intended as a base class that should only make the members of its own base class (B) available to classes that are derived from it (i.e.,D).Private inheritance should also be used when a derived class is-a certaintype of base class, but in order to initialize that base class an object ofanother class type must be available. Example: a newistream class-type(say: a streamIRandStream from which random numbers can be extracted) isderived fromstd::istream. Although anistream can be constructedempty (receiving itsstreambuf later using itsrdbuf member), it isclearly preferable to initialize theistream base class right away.
Assuming that aRandbuffer: public std::streambuf has been created forgenerating random numbers thenIRandStream can be derived fromRandbuffer andstd::istream. That way theistream base class canbe initialized using theRandbuffer base class.
As aRandStream is definitely not aRandbufferpublic inheritanceisnot appropriate. In this caseIRandStreamis-implemented-in-terms-of aRandbuffer and soprivate inheritanceshould be used.
IRandStream's class interface should therefore start like this:
class IRandStream: private Randbuffer, public std::istream { public: IRandStream(int lowest, int highest) // defines the range : Randbuffer(lowest, highest), std::istream(this) // passes &Randbuffer {} ... };Public inheritance should be reserved for classes for which the LSP holdstrue. In those cases the derived classes can always be used instead of thebase class from which they derive by code merely using base class references,pointers or members (I.e., conceptually the derived classis-a baseclass). This most often applies to classes derived from base classes offeringvirtual members. To separate the user interface from the redefinable interfacethe base class's public interface shouldnot contain virtual members(except for the virtual destructor) and the virtual members should all be inthe base class's private section. Such virtual members can still be overriddenby derived classes (this should not come as a surprise, considering howpolymorphism is implemented) and this design offers the base class fullcontrol over the context in which the redefined members are used. Often thepublic interface merely calls a virtual member, but those members can alwaysbe redefined to perform additional duties.
The prototypical form of a base class thereforelooks like this:
class Base { public: virtual ~Base(); void process(); // calls virtual members (e.g., // v_process) private: virtual void v_process(); // overridden by derived classes };Alternatively a base class may offer a non-virtual destructor, whichshould then be protected. It shouldn't be public to prevent deleting objectsthrough their base class pointers (in which case virtual destructors should beused). It should be protected to allow derived class destructors to call theirbase class destructors. Such base classes should, for the same reasons, havenon-public constructors and overloaded assignment operators.
std::streambuf receives the character sequencesprocessed by streams and defines the interface between stream objects anddevices (like a file on disk). Astreambuf object is usually not directlyconstructed, but usually it is used as base class of some derived classimplementing the communication with some concrete device.The primary reason for existence of the classstreambuf is to decouplethestream classes from the devices they operate upon. The rationale hereis to add an extra layer between the classes allowing us to communicate withdevices and the devices themselves. This implements achain of commandwhich is seen regularly in software design.
Thechain of command is considered a generic pattern when designingreusable software, encountered also in, e.g., theTCP/IP stack.
Astreambuf provides yet another example of the chain of commandpattern: the program talks tostream objects, which in turn forwardrequests tostreambuf objects, which in turn communicate with thedevices. Thus, as we will see shortly, we are able to do in user-software whathad to be done via (expensive) system calls before.
The classstreambuf has no public constructor, but does make availableseveral public member functions. In addition to these public member functions,several member functions are only available to classes derived fromstreambuf. In section14.8.3 a predefined specialization of theclassstreambuf is introduced. All public members ofstreambufdiscussed here arealso available infilebuf.
The next section shows thestreambuf members that may be overriddenwhen deriving classes fromstreambuf. Chapter26 offersconcrete examples of classes derived fromstreambuf.
The classstreambuf is used by streams performing input operations andby streams performing output operations and their member functions can beordered likewise. The typestd::streamsize used below may,for all practical purposes, be considered equal to the typesize_t.
When inserting information intoostream objects the information iseventually passed on to theostream'sstreambuf. Thestreambuf maydecide to throw an exception. However, this exception does not leave theostream using thestreambuf. Rather, the exception is caught by theostream, which sets itsios::bad_bit. Exceptions thrown bymanipulators which are inserted intoostream objects arenot caught byostream objects.
Public members for input operations
std::streamsize in_avail():EOF position).int sbumpc():EOF is returned. The returned character is removed from thestreambuf object. If no input is available,sbumpc calls the (protected) memberuflow (see section14.8.1 below) to make new characters available.EOF is returned if no more characters are available.int sgetc():EOF is returned. The character isnot removed from thestreambuf object (i.e., thestreambif's offset position isn't incremented). To remove a character from thestreambuf object,sbumpc (orsgetn) can be used.int sgetn(char *buffer, std::streamsize n):n characters are retrieved from the input buffer, and stored inbuffer. The actual number of characters read is returned. The (protected) memberxsgetn (see section14.8.1 below) is called to obtain the requested number of characters.int snextc():EOF is returned. The character isnot removed from thestreambuf object.int sputbackc(char c):c into thestreambuf's buffer to be returned as the next character to read from thestreambuf object. Caution should be exercised when using this function: often there is a maximum of just one character that can be put back.int sungetc():Public members for output operations
int pubsync():streambuf's buffer to the device. Normally only used by classes derived fromstreambuf.int sputc(char c):c is inserted into thestreambuf object. If, after writing the character, the buffer is full, the function calls the (protected) member functionoverflow to flush the buffer to the device (see section14.8.1 below).int sputn(char const *buffer, std::streamsize n):n characters frombuffer are inserted into thestreambuf object. The actual number of characters inserted is returned. This member function calls the (protected) memberxsputn (see section14.8.1 below) to insert the requested number of characters.Public members for miscellaneous operations
The next three members are normally only used by classes derived fromstreambuf.
ios::pos_type pubseekoff(ios::off_type offset, ios::seekdir way, ios::openmode mode = ios::in | ios::out):offset, relative to the standardios::seekdir values indicating the direction of the seeking operation.ios::pos_type pubseekpos(ios::pos_type pos, ios::openmode mode = ios::in | ios::out):pos.streambuf *pubsetbuf(char* buffer, std::streamsize n):streambuf object is going to usebuffer, which may contain at leastn characters.streambuf are important forunderstanding and usingstreambuf objects. Although there are bothprotecteddata members and protected member functions defined in theclass streambuf using the protecteddata members is strongly discouraged as using them violates the principleofdata hiding. Asstreambuf's set of member functions is quiteextensive, it is hardly ever necessary to use its data members directly. Thefollowing subsections do not even list all protected member functions but onlythose are covered that are useful for constructing specializations.Streambuf objects control a buffer, used for input and/or output, forwhich begin-, actual- and end-pointers have been defined, as depicted infigure19.

Streambuf offers two protected constructors:
streambuf::streambuf():class streambuf.streambuf::streambuf(streambuf const &rhs):class streambuf. Note thatthis copy constructor merely copies the values of the data members ofrhs:after using the copy constructor bothstreambuf objects refer to the samedata buffer and initially their pointers point at identical positions. Alsonote that these arenot shared pointers, but only `raw copies'.virtual may of course be redefined in derivedclasses:char *eback():Streambuf maintains three pointers controlling its input buffer:eback points to the `end of the putback' area: characters can safely be put back up to this position. See also figure19.Eback points to thebeginning of the input buffer.char *egptr():Egptr points just beyond the last character that can be retrieved from the input buffer. See also figure19. Ifgptr equalsegptr the buffer must be refilled which is handled by memberunderflow, see below.void gbump(int n):gptr (see below) is advanced overn positions.char *gptr():Gptr points to the next character to be retrieved from the object's input buffer. See also figure19.virtual int pbackfail(int c):c fails. One might consider restoring the old read pointer when input buffer's begin has been reached. This member function is called when ungetting or putting back a character fails. In particular, it is called whengptr() == 0: no buffering used,gptr() == eback(): no more room to push back,*gptr() != c: a different character than the next character to be read must be pushed back.c == endOfFile() then the input device must be reset by one character position. Otherwisec must be prepended to the characters to be read. The function should returnEOF on failure. Otherwise 0 can be returned.void setg(char *beg, char *next, char *beyond):beg points to the beginning of the input area,next points to the next character to be retrieved, andbeyond points to the location just beyond the input buffer's last character. Oftennext is at leastbeg + 1, to allow a put back operation. No input buffering is used when this member is called assetg(0, 0, 0). See also the memberuflow, below.virtual streamsize showmanyc():uflow orunderflow returnsEOF. By default 0 is returned (meaning no or some characters are returned before the latter two functions returnEOF). When a positive value is returned then the next call ofu(nder)flow does not returnEOF.virtual int uflow():underflow (see below) to reload the input buffer. Ifunderflow fails,EOF is returned. Otherwise, the next available character (*gptr()) is returned as anunsigned char, and then incrementsgptr. This is different fromunderflow, which merely returns the next available character, without incrementinggptr's position.When thestreambuf doesn't use input buffering this function, rather thanunderflow, can be overridden to produce the next available character from the device.
virtual int underflow():EOF.It is called when
eback() == 0)gptr() >= egptr(): the input buffer is exhausted.Often, when buffering is used, the complete buffer is not refreshed as this would make it impossible to put back characters immediately following a reload. Instead, buffers are often refreshed in halves. This system is called asplit buffer.
Classes derived fromstreambuf for reading normally at least overrideunderflow. The prototypical example of an overriddenunderflow function looks like this:
int underflow(){ if (not refillTheBuffer()) // assume a member d_buffer is available return EOF; // reset the input buffer pointers setg(d_buffer, d_buffer, d_buffer + d_nCharsRead); // return the next available character // (the cast is used to prevent // misinterpretations of 0xff characters // as EOF) return static_cast<unsigned char>(*gptr());} This example can be used by streams reading the information made available by devices. Section14.8.2 covers a more complex situation: stream supporting both input and output.virtual streamsize xsgetn(char *buffer, streamsize n):n characters from the input device. The default implementation is to callsbumpc for every single character meaning that by default this member (eventually) callsunderflow for every single character. The function returns the actual number of characters read orEOF. OnceEOF is returned thestreambuf stops reading the device (see also section14.8.2.)virtual int overflow(int c):c is initialized to the next character to be processed. If no output buffering is usedoverflow is called for every single character that is written to thestreambuf object. No output buffering is accomplished by setting the buffer pointers (using,setp, see below) to 0. Thedefault implementation returnsEOF, indicating that no characters can be written to the device.Classes derived fromstreambuf for writing normally at least overrideoverflow. The prototypical example of an overriddenoverflow function looks like this (see also section14.8.2):
int OFdStreambuf::overflow(int c){ sync(); // flush the buffer if (c != EOF) // write a character? { *pptr() = static_cast<char>(c); // put it into the buffer pbump(1); // advance the buffer's pointer } unsigned char ch = c; return ch;}char *pbase():Streambuf maintains three pointers controlling its output buffer:pbase points to the beginning of the output buffer area. See also figure19.char *epptr():Streambuf maintains three pointers controlling its output buffer:epptr points just beyond the output buffer's last available location. See also figure19. Ifpptr (see below) equalsepptr the buffer must be flushed. This is implemented by callingoverflow, see before.void pbump(int n):pptr (see below) is advanced byn positions. The next character will be written at that location.char *pptr():Streambuf maintains three pointers controlling its output buffer:pptr points to the location in the output buffer where the next available character will be written (note that in order to write a characterpptr() must point to a location in the rangepbase() toepptr()). See also figure19.void setp(char *beg, char *beyond):Streambuf's output buffer is initialized to the locations passed tosetp.Beg points to the beginning of the output buffer andbeyond points just beyond the last available location of the output buffer. Usesetp(0, 0) to indicate thatno buffering should be used. In that caseoverflow is called for every single character to be written to the device.virtual streamsize xsputn(char const *buffer, streamsize n):n characters to the output buffer. The actual number of inserted characters is returned. IfEOF is returned writing to the device stops. The default implementation callssputc for each individual character. Redefine this member if, e.g., thestreambuf should support theios::openmode ios::app. Assuming the classMyBuf, derived fromstreambuf, features a data memberios::openmode d_mode (representing the requestedios::openmode), and a memberwrite(char const *buf, streamsize len) (writinglen bytes atpptr()), then the following code acknowledges theios::app mode (see also section14.8.2):std::streamsize MyStreambuf::xsputn(char const *buf, std::streamsize len){ if (d_openMode & ios::app) seekoff(0, ios::end); return write(buf, len);}virtual streambuf *setbuf(char *buffer, streamsize n):pubsetbuf.virtual ios::pos_type seekoff(ios::off_type offset, ios::seekdir way, ios::openmode mode = ios::in | ios::out):ios::beg, ios::cur orios::end). The default implementation indicates failure by returning -1. This function is called whentellg ortellp are called. When derived class supports seeking, then it should also define this function to handle repositioning requests. It is called bypubseekoff. The new position or (by default) an invalid position (i.e., -1) is returned (see also section14.8.2).virtual ios::pos_type seekpos(ios::pos_type offset, ios::openmode mode = ios::in | ios::out):ios::beg). This function is called whenseekg orseekp are called. The new position or (by default) an invalid position (i.e., -1) is returned.virtual int sync():streambuf object ceases to exist.streambuf at leastunderflow shouldbe overridden by classes intending to read information from devices, andoverflow should be overridden by classes intending to write information todevices. Several examples of classes derived fromstreambuf are providedin chapter26.Fstream class type objects use a combined input/output buffer,resulting fromistream andostream being virtually derived fromios, which class defines astreambuf. To construct a classsupporting both input and output using separate buffers, thestreambufitself may define two buffers. Whenseekoff is called for reading, amode parameter can be set toios::in, otherwise toios::out. Thusthe derived class knows whether it should access theread buffer or thewrite buffer. Of course,underflow andoverflow do not have toinspect the mode flag as they by implication know on which buffer they shouldoperate.
underflowespecially when buffers must repeatedly be refreshed and loaded. They are:underflow andoverflow.uflow be overridden?xsgetn.xsputn.
Figure20 shows a situation where multiple buffers are used: thedevice's information is made available in a buffer which is processed andmanaged by the derivedstreambuf class. In this figure the followingvariables are introduced:
offset is the device's current position. Its lower limit is 0, and for all practical purposes there is no upper limit.maxEnd, however, is the device's current physical maximum offset value. Such a physical maximum may not exist, but itdoes exist if, e.g., a physical buffer in memory is used which cannot contain more than a fixed number of bytes. In those casesmaxEnd is set to that maximum value, representing the offset just beyond the highest offset where a byte may be written to the device;getEnd is the current maximum number of characters that can be read from the device. With a newly defined device its value is 0 (zero), but once bytes are written to the devicegetEnd is updated to the position just beyond to the highest ever offset where a byte was written. When disk-files are usedgetEnd would be equal to the file's size;streambuf's buffer. Instead it is split up in blocks of a fixed size. The device's offset of the first byte of such a block is available inbufBeg, and the offset just beyond the last byte of such a block is available inbufEnd;bufEnd never exceedsmaxEnd.This section covers how such multi-buffer data can be handled byiostreamobjects: streams supporting readingand writing. Such streams offerseekg andseekp members, but the device's offset position applies tobothseekg andseekp: after eitherseekg orseekp reading andwriting both start at the position defined by either of these two seekmembers. Furthermore, when switching between reading and writing noseekg/seekp call is required: by default new read or write requestscontinue at the device's current offset, set by the last read, write, or seekrequest.
seekg andseekp simplycompute the requested offset. Seek requests specifyingios::beg changeoffset to the requested value. Whenios::end is specified the offsetis computed relative togetEnd (sincegetEnd corresponds to the filesize of a physical disk-fileios::end should usegetEnd as the currentend-offset and notmaxEnd).Computing offsets as shifts relative to the current offset is slightlymore complicated. When so far neither reading nor writing has been usedthings are easy: the new offset equals the current offset plus the specifiedios::off_type sthift. But once information has just been read or writtenthings get complicated becauseoffset doesn't correspond anymore tothe actual offset.
For example, initially, when information is written to the device'soffset,bufBeg andbufEnd are computed so thatoffset is located inside thebuffer starting atbufBegin and continuing tobufEnd, but thereaftersubsequent write requests are handled by the stream itself, and thereforeoffset isn't updated. Instead onlypptr()'s location, is updated,invalidatingoffset.
Assume buffers are 100 bytes large, and in a concrete situation the buffercovers the device's offsets 500 to 600 whileoffset equals 510. Then,after writing"hello" pptr() is 515, butoffset is still at510. Consequently, in that situation issuingseekp(-5, ios::cur).tellp()should not return 5 (i.e.,offset - 5), but 10:bufBegin + pptr() -pbase() - 5. A similar situation is encountered when reading:gptr() isupdated by read operations.
This problem is solved by introducing three states: thestreambuf objectstarts in theSEEK-state: the last-used operation wasn't reading orwriting. Once reading is used the state changes toREAD, and toWRITEonce writing is used.
When s seek-request is issued the relative position depends on the currentstate: in stateSEEK the seek's shift value (as inseekg(shift,ios::cur))is added tooffset, in stateREAD it's added tobufBegin+ gptr() - eback(), and in stateWRITE it's added tobufBegin + pptr()- pbase(). Seek-requests also change the state toSEEK, so subsequentseek requests are computed relative to the last computedoffset. Finally,to ensure thatunderflow andoverflow are called when subsequent reador write operations are requested seek requests also resetsetg andsetp by calling them with 0 (zero) arguments. Here is a skeletonimplementation ofseekoff (assumingusing namespace std):
ios::pos_type StreamBuf::seekoff(ios::off_type step, ios::seekdir way, ios::openmode mode){ off_type offs; switch (way) { default: // ios::beg: buffOffset is step offs = step; break; case ios::cur: switch (d_last) { // default: case SEEK default: // no read/write used so far offs = offset; break; // add step to bufOffset (below) case READ: // setg was used, set bufOffset to // the abs offset of gptr() offs = bufbeg + gptr() - eback(); break; case WRITE: // setp was used, set bufOffset to // the abs offset of pptr() offs = bufbeg + pptr() - pbase(); // may extend the writing area if (offs > static_cast<off_type>(getend)) getend = offs; break; } offs += step; // add the step break; case ios::end: offs = getend + step; // shift from the last write position break; } if (offs < 0) offs = 0; // offset always >= 0 d_last = SEEK; setg(0, 0, 0); // reset the buffers setp(0, 0); return offset = offs; // the updated offset}overflow is called then the stream's write buffer is empty orexhausted. The memberunderflow is called then the stream's read buffer isempty or exhausted.In both cases a new buffer (frombufBeg tobufEnd) is computedcontainingoffset. Butoffset depends on the streambuf's state. Whencalled in theSEEK stateoffset is up-to-date; when called in theWRITE state the offset is at the last-used write offset; when called intheREAD state the current offset is at the end of the current readbuffer.
When called in theSEEK state the read and write buffers are alreadyempty. When called in theREAD state the actual offset is determined andthe read buffer is reset to ensure thatunderflow is called at the nextread operation.
Likewise, when called in theWRITE state the write buffer is reset toensure thatoverflow is called at the next write request.
Bothunderflow andoverflow therefore start by determining the currentoffset, computing the corresponding buffer boundaries. The membergetOffsetis called by bothunderflow andoverflow. Here's its skeletonimplementation:
size_t StreamBuf::getOffset(){ size_t offs; switch (d_last) { default: // no buffers so far: use offset offs = offset; break; case READ: // use the lastused read offset offs = bufbeg + (gptr() - eback()); setg(0, 0, 0); break; case WRITE: // use the lastused write offset offs = bufbeg + (pptr() - pbase()); setp(0, 0); break; } bufLimits(offs); // set the buffer limits return offs;}The memberbufLimits simply ensures thatoffset is located inside abuffer:
void StreamBuf::bufLimits(size_t offset){ bufbeg = offset / blockSize * blockSize; bufend = bufbeg + blockSize; if (bufend > maxend) // never exceed maxend bufend = maxend;}The memberoverflow returnsEOF or initializes a new readbuffer. Sinceoverflow is guaranteed to be called when writing isrequested from statesSEEK andREAD it callsgetOffset to obtainthe current absolute offset and the correspondingbufBeg enbufEndvalues. Writing can only be used ifoffset < maxEnd. If so, a new writebuffer is installed whosepptr() points at theoffset position in thephysical device. After callingsetpoverflowmust returnch onsuccess. Here'soverflow's skeleton:
int StreamBuf::overflow(int ch) // writing{ size_t offs = getOffset(); if (offs >= maxend) // at maxend: no more space return EOF; // define the writing buffer setp(allData + bufbeg, allData + bufend); pbump(offs - bufbeg); // go to the pos. to write the next ch *pptr() = ch; // write ch to the buffere pbump(1); ++offset; // maybe enlarge getend getend = max(getend, bufbeg + (pptr() - pbase())); d_last = WRITE; // change to writing mode // return the last written char return static_cast<unsigned char>(ch);}The memberunderflow returnsEOF or initializes a new readbuffer. Sinceunderflow is guaranteed to be called when reading isrequested from statesSEEK andWRITE it calls, likeoverflow,getOffset to obtain the current absolute offset as well as thematchingbufBeg enbufEnd values. Reading can only be used ifoffset < getEnd. If so, a new read buffer is installed whosegptr()points at theoffset position in the physical device.
As withoverflow, after callingsetg it'sessential that the firstavailable character (i.e.,*gptr()) is returned. If not and the buffercontains just one character then that character might not be processed byunderflow's caller. Here'sunderflow's skeleton:
int StreamBuf::underflow(){ offset = getOffset(); if (offset >= getend) // beyond the reading area return EOF; // define the reading buffer setg(allData + bufbeg, allData + offset, allData + min(getend, bufend)); d_last = READ; return static_cast<unsigned char>(*gptr());}uflow be overridden? The functionuflow is called when it'savailable to return the next character from the device. In practice this ishandled byunderflow, so there's probably little need for overridinguflow. Butifuflow is overridden then it must return the nextavaialble character and updategptr() to the next availablecharacter. If the update isn't performed then the returned characters isreceived twice by the stream. Here's a skeleton:int StreamBuf::uflow(){ if (gptr() == egptr() and underflow() == EOF) return EOF; unsigned char ch = *gptr(); gbump(1); return ch;}seek requestsseekg, seekp callseekoff. The memberstreambuf::seekpos is maybe also called, but in practiceseekpos callsseekoff;get callsunderflow if there's no (or an exhausted) read buffer. Otherwise it returns the character atgptr(), incrementing its position;>> callsunderflow if there's no (or an exhausted) read buffer. Otherwise if white-space characters are ignored (which is the default) all white-space characters are skipped and the stream reads the matching bytes from the read buffer, refreshing the buffer when needed;read callsxsgetn (which itself callsunderflow) if there's no or an exhausted read buffer, and tries to read the requested number of characters from the device;rdbuf() is used to insert the device's content from its current offset position. It callsunderflow and reads all the buffer's characters untilunderflow returnsEOF.put callsoverflow if there's no (or a filled up) write buffer. Otherwise it returns the character atpptr(), incrementing its position;<< callsoverflow if there's no (or a filled up) write buffer. Otherwise the argument may be converted to characters (like when insterting anint value) and the resulting characters are inserted into the device, refreshing the buffer when needed;write callsxsputn (which itself callsoverflow) if there's no or a filled up wrte buffer, and tries to write the requested number of characters to the device.read member callsxsgetn to readnChars characters fromthe device. ThenChar characters might not be available in the currentbuffer. In those casesunderflow is called to refresh thebuffer. Initially some bytes may already be available. At each cycle thenumber of available characters are copied to the next location of itsbufparameter, callingunderflow if there are no available charactersanymore. Soxsgetn, while there are (still) characters to be read from thedevice, mustunderflow;buf parameter;buf pointer and the counters are updated using the number of read bytes.Here is a skeleton ofxsgetn:
streamsize StreamBuf::xsgetn(char *buf, streamsize nChars){ size_t toRead = nChars; size_t nRead = 0; while (toRead) { size_t avail = egptr() - gptr(); // available buffer space // no or empty memory buffer // but no more readable chars if (avail == 0 and underflow() == EOF) return nRead; avail = min(getend, bufend) - offset; size_t next = min(avail, toRead); // next #bytes to write memcpy(buf, gptr(), next); // write to the buffer gbump(next); // update gptr buf += next; // update the buf location toRead -= next; // and update the counters nRead += next; offset += next; } d_last = READ; // now reading: also if underflow // wasn't called. return nRead;}write member callsxputn to writenChars charactersinto the device. As withxsgetn thenChar characters might not beavailable in the current buffer. In those casesoverflow is called torefresh the buffer. Room for some bytes may still be available, and at eachcycle the number of available locations are copied from the member'sbufparameter to thestreambuf's write buffer, callingoverflow if there'sno space available anymore in the current write buffer. Soxsputn, whilethere are (still) characters to be written to the device, mustoverflow;buf pointer and the counters are updated using the number of written bytes.Here is a skeleton ofxsputn:
streamsize StreamBuf::xsputn(char const *buf, streamsize nChars){ size_t toWrite = nChars; size_t nWritten = 0; size_t offs = getOffset(); size_t avail = epptr() - pptr(); // available buffer space while (toWrite) { if (avail == 0) // no space: try to reload { // the buffer if (overflow(*buf) == EOF) // storage space exhausted? break; // yes: done ++buf; // no: 1 byte was written ++nWritten; ++offs; --toWrite; avail = epptr() - pptr(); // remaining buffer space if (avail == 0) // next cycle if avail == 0 continue; } size_t next = min(avail, toWrite); // next #bytes to write memcpy(pptr(), buf, next); // write to the buffer pbump(next); // update pptr buf += next; // update the buf location nWritten += next; // and update the counters toWrite -= next; offset = offs += next; } if (getend < offs + nWritten) // maybe enlarge the reading getend = offs + nWritten; // area d_last = WRITE; // WRITE state: now writing, // maybe overflow wasn't used return nWritten;}classfilebuf is a specialization ofstreambuf used by thefilestream classes. Before using afilebuf the header file<fstream> must be included.In addition to the (public) members that are available through theclassstreambuf,filebuf offers the following (public) members:
filebuf():Filebuf offers a public constructor. It initializes a plainfilebuf object that is not yet connected to a stream.bool is_open():True is returned if thefilebuf is actually connected to an open file,false otherwise. See theopen member, below.filebuf *open(char const *name, ios::openmode mode):filebuf object with a file whose name is provided. The file is opened according to the providedopenmode.filebuf *close():filebuf object and its file. The association is automatically closed when thefilebuf object ceases to exist.Exception whoseprocess memberwould behave differently, depending on the kind of exception that wasthrown. Now that we've introducedpolymorphism we can further develop thisexample.It probably does not come as a surprise that our classException should bea polymorphic base class from which special exception handling classes can bederived. In section10.3.1 a memberseverity was used offeringfunctionality that may be replaced by members of theException base class.
The base classException may be designed as follows:
#ifndef INCLUDED_EXCEPTION_H_ #define INCLUDED_EXCEPTION_H_ #include <iostream> #include <string> class Exception { std::string d_reason; public: Exception(std::string const &reason); virtual ~Exception(); std::ostream &insertInto(std::ostream &out) const; void handle() const; private: virtual void action() const; }; inline void Exception::action() const { throw; } inline Exception::Exception(std::string const &reason) : d_reason(reason) {} inline void Exception::handle() const { action(); } inline std::ostream &Exception::insertInto(std::ostream &out) const { return out << d_reason; } inline std::ostream &operator<<(std::ostream &out, Exception const &e) { return e.insertInto(out); } #endif Objects of this class may be inserted intoostreams but the coreelement of this class is the virtual member functionaction, by defaultrethrowing an exception.A derived classWarning simply prefixes the thrown warning text by thetextWarning:, but a derived classFatal overridesException::action by callingstd::terminate, forcefullyterminating the program.
Here are the classesWarning andFatal
#ifndef WARNINGEXCEPTION_H_ #define WARNINGEXCEPTION_H_ #include "exception.h" class Warning: public Exception { public: Warning(std::string const &reason) : Exception("Warning: " + reason) {} }; #endif #ifndef FATAL_H_ #define FATAL_H_ #include "exception.h" class Fatal: public Exception { public: Fatal(std::string const &reason); private: void action() const override; }; inline Fatal::Fatal(std::string const &reason) : Exception(reason) {} inline void Fatal::action() const { std::cout << "Fatal::action() terminates" << '\n'; std::terminate(); } #endifWhen the example program is started without arguments it throws aFatal exception, otherwise it throws aWarning exception. Ofcourse, additional exception types could also easily be defined. To make theexample compilable theException destructor is defined abovemain. Thedefault destructor cannot be used, as it is a virtual destructor. In practicethe destructor should be defined in its own little source file:
#include "warning.h" #include "fatal.h" Exception::~Exception() {} using namespace std; int main(int argc, char **argv) try { try { if (argc == 1) throw Fatal("Missing Argument") ; else throw Warning("the argument is ignored"); } catch (Exception const &e) { cout << e << '\n'; e.handle(); } } catch(...) { cout << "caught rethrown exception\n"; }The fundamental idea behind polymorphism is that the compiler does not knowwhich function to call at compile-time. The appropriate function isselected at run-time. That means that the address of the function must beavailable somewhere, to be looked up prior to the actual call. This`somewhere' place must be accessible to the object in question. So when aVehicle *vp points to aTruck object, thenvp->mass() callsTruck's member function. The address of this function is obtained throughthe actual object to whichvp points.
Polymorphism is commonly implemented as follows: an object containing virtualmember functions also contains, usually as its first data member ahidden data member, pointing to an array containing the addresses of theclass's virtual member functions. The hidden data member is usually called thevpointer, the array of virtual member function addresses thevtable.
The class's vtable is shared by all objects of that class. The overhead ofpolymorphism in terms ofmemory consumption is therefore:
vp->mass first inspects the hiddendata member of the object pointed to byvp. In the case of the vehicleclassification system, this data member points to a table containing twoaddresses: one pointer to the functionmass and one pointer to thefunctionsetMass (three pointers if the class also defines (as itshould) a virtual destructor). The actually called function is determined fromthis table.The internal organization of the objects having virtual functions isillustrated in Figure21 and Figure22(originals provided byGuillaume Caumon).


As shown by Figure21 and Figure22,objects potentially using virtual member functions must have one (hidden) datamember to address a table of function pointers. The objects of the classesVehicle andCar both address the same table. The classTruck,however, overridesmass. Consequently,Truck needs its own vtable.
A small complication arises when a class is derived from multiple baseclasses, each defining virtual functions. Consider the following example:
class Base1 { public: virtual ~Base1(); void fun1(); // calls vOne and vTwo private: virtual void vOne(); virtual void vTwo(); }; class Base2 { public: virtual ~Base2(); void fun2(); // calls vThree private: virtual void vThree(); }; class Derived: public Base1, public Base2 { public: ~Derived() override; private: void vOne() override; void vThree() override; };In the exampleDerived is multiply derived fromBase1 andBase2,each supporting virtual functions. Because of this,Derived also hasvirtual functions, and soDerived has avtable allowing a base classpointer or reference to access the proper virtual member.
WhenDerived::fun1 is called (or aBase1 pointer pointing to aDerived object callsfun1) thenfun1 callsDerived::vOne andBase1::vTwo. Likewise, whenDerived::fun2 is calledDerived::vThree is called.
The complication occurswithDerived's vtable. Whenfun1 is called its class type determinesthe vtable to use and hence which virtual member to call. So whenvOne iscalled fromfun1, it is presumably the second entry inDerived'svtable, as it must match the second entry inBase1's vtable. However, whenfun2 callsvThree it apparently is also the second entry inDerived's vtable as it must match the second entry inBase2's vtable.
Of course this cannot be realized by a single vtable. Therefore, when multipleinheritance is used (each base class defining virtual members) anotherapproach is followed to determine which virtual function to call. In thissituation (cf. figure Figure23) the classDerived receivestwovtables, one for each of its base classes and eachDerivedclass object harborstwo hidden vpointers, each one pointing to itscorresponding vtable.

Since base class pointers, base class references, or base class interfacemembers unambiguously refer to one of the base classes the compiler candetermine which vpointer to use.
The following therefore holds true for classes multiply derived from baseclasses offering virtual member functions:
In function `Derived::Derived()': : undefined reference to `vtable for Derived'
This error is generated when a virtual function's implementation ismissing in a derived class, but the function is mentioned in the derivedclass's interface.
Such a situation is easily encountered:
undefined reference to `vtable for Derived'
class Base { virtual void member(); }; inline void Base::member() {} class Derived: public Base { void member() override; // only declared }; int main() { Derived d; // Will compile, since all members were declared. // Linking will fail, since we don't have the // implementation of Derived::member() } It's of course easy to correct the error: implement the derived class'smissing virtual member function.Virtual functions shouldnever be implemented inline. Since the vtablecontains the addresses of the class's virtual functions, these functions musthave addresses and so they must have been compiled as real (out-of-line)functions. By defining virtual functions inline you run the risk that thecompiler simply overlooks those functions as they may very well never beexplicitly called (but only polymorphically, from a base class pointer orreference). As a result their addresses may never enter their class's vtables(and even the vtable itself might remain undefined), causing linkage problemsor resulting in programs showing unexpected behavior. All these kinds ofproblems are simply avoided:never define virtual members inline (see alsosection7.8.2.1).
According to thePrototype Design Pattern each derived class is giventhe responsibility of implementing a member function returning a pointer to acopy of the object for which the member is called. The usual name for thisfunction isclone. Separating the user interface from the reimplementationinterfaceclone is made part of the interface andnewCopy is definedin the reimplementation interface. A base class supporting `cloning' defines avirtual destructor,clone, returningnewCopy's return value and thevirtual copy constructor, a pure virtual function, having the prototypevirtual Base *newCopy() const = 0. AsnewCopy is a pure virtualfunction all derived classes must now implement their own `virtualconstructor'.
This setup suffices in most situations where we have a pointer orreference to a base class, but it fails when used with abstractcontainers. We can't create avector<Base>, withBase featuring thepure virtualcopy member in its interface, asBase is called toinitialize new elements of such a vector. This is impossible asnewCopy is apure virtual function, so aBase object can't be constructed.
The intuitive solution, providingnewCopy with a defaultimplementation, defining it as an ordinary virtual function, fails too as thecontainer callsBase(Base const &other), which would have to callnewCopy to copyother. At this point it is unclear what to do withthat copy, as the newBase object already exists, and contains noBasepointer or reference data member to assignnewCopy's return value to.
Alternatively (and preferred) the originalBase class (defined as anabstract base class) is kept as-is and a wrapper classClonable is usedto manage theBase class pointers returned bynewCopy. In chapter17 ways to mergeBase andClonable into one class arediscussed, but for now we'll defineBase andClonable as separateclasses.
The classClonable is a very standard class. It contains a pointer memberso it needs a copy constructor, destructor, and overloaded assignmentoperator. It's given at least one non-standard member:Base &base() const,returning a reference to the derived object to whichClonable'sBase *data member refers. It is also provided with an additional constructor toinitialize itsBase * data member.
Any non-abstract class derived fromBase must implementBase*newCopy(), returning a pointer to a newly created (allocated) copy of theobject for whichnewCopy is called.
Once we have defined a derived class (e.g.,Derived1), we can put ourClonable andBase facilities to good use. In the next example we seemain defining avector<Clonable>. An anonymousDerived1object is then inserted into the vector using the following steps:
Derived1 object is created;Clonable usingClonable(Base *bp);Clonable object is inserted into the vector,usingClonable's move constructor. There are only temporaryDerivedandClonable objects at this point, so no copy construction is required.Clonable object containing theDerived1* is used. No additional copies need to be made (or destroyed).Next, thebase member is used in combination withtypeid to showthe actual type of theBase & object: aDerived1 object.
Main then contains the interesting definitionvector<Clonable>v2(bv). Here a copy ofbv is created. This copy construction observesthe actual types of theBase references, making sure that the appropriatetypes appear in the vector's copy.
At the end of the program, we have created twoDerived1 objects, whichare correctly deleted by the vector's destructors. Here is the full program,illustrating the `virtual constructor' concept ( Jesse van den Kieboom created an alternative implementation of a classClonable, implemented as aclass template. His implementation is found in the source archive undercontrib/classtemplates/.):
#include <iostream> #include <vector> #include <algorithm> #include <typeinfo>// Base and its inline member: class Base { public: virtual ~Base(); Base *clone() const; private: virtual Base *newCopy() const = 0; }; inline Base *Base::clone() const { return newCopy(); }// Clonable and its inline members: class Clonable { Base *d_bp; public: Clonable(); explicit Clonable(Base *base); ~Clonable(); Clonable(Clonable const &other); Clonable(Clonable &&tmp); Clonable &operator=(Clonable const &other); Clonable &operator=(Clonable &&tmp); Base &base() const; }; inline Clonable::Clonable() : d_bp(0) {} inline Clonable::Clonable(Base *bp) : d_bp(bp) {} inline Clonable::Clonable(Clonable const &other) : d_bp(other.d_bp->clone()) {} inline Clonable::Clonable(Clonable &&tmp) : d_bp(tmp.d_bp) { tmp.d_bp = 0; } inline Clonable::~Clonable() { delete d_bp; } inline Base &Clonable::base() const { return *d_bp; }// Derived and its inline member: class Derived1: public Base { public: ~Derived1() override; private: Base *newCopy() const override; }; inline Base *Derived1::newCopy() const { return new Derived1(*this); }// Members not implemented inline: Base::~Base() {} Clonable &Clonable::operator=(Clonable const &other) { Clonable tmp(other); std::swap(d_bp, tmp.d_bp); return *this; } Clonable &Clonable::operator=(Clonable &&tmp) { std::swap(d_bp, tmp.d_bp); return *this; } Derived1::~Derived1() { std::cout << "~Derived1() called\n"; }// The main function: using namespace std; int main() { vector<Clonable> bv; bv.push_back(Clonable(new Derived1())); cout << "bv[0].name: " << typeid(bv[0].base()).name() << '\n'; vector<Clonable> v2(bv); cout << "v2[0].name: " << typeid(v2[0].base()).name() << '\n'; } /* Output: bv[0].name: 8Derived1 v2[0].name: 8Derived1 ~Derived1() called ~Derived1() called */