Movatterモバイル変換


[0]ホーム

URL:




Chapter 14: Polymorphism

Using inheritance classes may be derived from other classes, called baseclasses. In the previous chapter we saw that base class pointers may be usedto point to derived class objects. We also saw that when a base class pointerpoints to an object of a derived class the pointer's type, rather than theobject's type, determines which member functions are visible. So when aVehicle *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,process must callDerived::hello ashello is called viaprocess from 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.

14.1: Virtual functions

By default the behavior of a member function called via a pointer or referenceis determined by the implementation of that function in the pointer's orreference's class. E.g., aVehicle * 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:

The example illustrates that when a pointer to a class is usedonly themembers of that class can be called. A member'svirtual 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.

14.1.1: Constructors of polymorhic classes

Although constructors of polymorphic classes may (indirectlly) call virtualmembers, that's probably not what you want as constructors of polymorphicclasses don't consider that those members may be overridden by derivedclasses. As an opening example: if the classVehicle 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).

14.2: Virtual destructors

When an object ceases to exist the object'sdestructor is called. Nowconsider the following code fragment (cf. section13.1):
    Vehicle *vp = new Land{ 1000, 120 };    delete vp;          // object destroyed

Heredelete 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.

14.3: Pure virtual functions

The base classVehicle 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.

14.3.1: Implementing pure virtual functions

Pure virtual member functions may be implemented. To implement a pure virtual member function, provide it with its normal= 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() called

Implementing 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.

14.4: Explicit virtual overrides

Consider the following situations: Two special identifiers,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;    };

14.5: Virtual functions and multiple inheritance

In chapter6 we encountered the classfstream, 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?

14.5.1: Ambiguity in multiple inheritance

Let's investigate closer why anAirCar introducesambiguity, whenderived fromCar andAir. The duplication ofVehicle data is further illustrated inFigure16.

Figure 16: Duplication of a base class in multiple derivation.

The internal organization of anAirCar is shown inFigure17

Figure 17: Internal organization of anAirCar object.

TheC++ compiler detects the ambiguity in anAirCar 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:

The second possibility is preferred as it does not require the compiler toflag an error; nor does it require the programmer using the classAirCar to take special precautions.

However, there exists a more elegant solution, discussed in the nextsection.

14.5.2: Virtual base classes

As illustrated in Figure17, anAirCar 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.

Figure 18: Internal organization of anAirCar 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:

Virtual derivation is, in contrast to virtual functions, a purecompile-time issue. Virtual inheritance merely defineshow the compiler defines a class's data organization and construction process.

14.5.3: When virtual derivation is not appropriate

Virtual inheritance can be used to merge multiply occurring baseclasses. However, situations may be encountered where multiple occurrences ofbase classesis appropriate. Consider the definition of aTruck (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    }

14.6: Run-time type identification

C++ offers two ways to retrieve types ofobjects and expressions at run-time. The possibilities ofC++'s run-timetype identification are limited compared to languages likeJava. Usuallystatic type checking andstatic type identification is used inC++. Static type checking is possibly safer and certainly more efficientthan run-time type identification and should therefore be preferred overrun-time type identification. But situations exist where run-time typeidentification is appropriate.C++ offers run-time type identificationthrough thedynamic cast andtypeid operators. These operators can be used with objects of classes having at least onevirtual member function.

14.6.1: The dynamic_cast operator

Thedynamic_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.

14.6.2: The `typeid' operator

As with thedynamic_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.

14.7: Inheritance: when to use to achieve what?

Inheritance should not be applied automatically and thoughtlessly. Oftencomposition can be used instead, improving on a class's design by reducingcoupling. When inheritance is usedpublic inheritance should notautomatically be used but the type of inheritance that is selected shouldmatch the programmer's intent.

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?

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.

14.8: The `streambuf' class

The classstd::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

Public members for output operations

Public members for miscellaneous operations

The next three members are normally only used by classes derived fromstreambuf.

14.8.1: Protected `streambuf' members

Theprotected members of the classstreambuf 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.

Figure 19: Input- and output buffer pointers of the class `streambuf'

Streambuf offers two protected constructors:

14.8.1.1: Protected members for input operations

Several protected member functions are available for input operations. Themember functions markedvirtual may of course be redefined in derivedclasses:

14.8.1.2: Protected members for output operations

The following protected members are available for outputoperations. Again, some members may be overridden by derived classes:

14.8.1.3: Protected members for buffer manipulation

Several protected members are related to buffer management and positioning:

14.8.1.4: Deriving classes from `streambuf'

When classes are derived fromstreambuf 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.

14.8.2: A streambuf used for input and output

Several complexities might be encountered when overridingunderflowespecially when buffers must repeatedly be refreshed and loaded. They are:

Figure 20: Multi-buffer I/O handling

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:

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.

14.8.2.1: Keeping track of the offset

Keeping track of the current offset is not a trivial problem. As long asneither reading nor writing has been usedseekg 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}

14.8.2.2: The members overflow and underflow

The memberoverflow 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());}

14.8.2.3: Overriding uflow?

Shoulduflow 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;}

14.8.2.4: When are which members called?

The following overview shows which members are called by whichstreamread/write/seek requests:

14.8.2.5: The member xsgetn

A stream'sread 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, must

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;}

14.8.2.6: The member xsputn

A stream'swrite 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, must

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;}

14.8.3: The class `filebuf'

Theclassfilebuf 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:

14.9: A polymorphic exception class

Earlier in theC++ Annotations (section10.3.1) we hinted at thepossibility of designing a classException 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();    }    #endif

When 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";    }

14.10: How polymorphism is implemented

This section briefly describes how polymorphism isimplemented inC++. It is not necessary to understand how polymorphism isimplemented if you just want touse polymorphism. However, we think it'snice to know how polymorphism is possible. Also, knowing how polymorphism isimplemented clarifies why there is a (small) penalty to using polymorphism interms of memory usage and efficiency.

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:

Consequently, a statement likevp->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).

Figure 21: Internal organization objects when virtual functions are defined.

Figure 22: Complementary figure, provided by Guillaume 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.

Figure 23: Vtables and vpointers with multiple base classes

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:

14.11: Undefined reference to vtable ...

Occasionaly, the linker generates an error like the following:
    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:

Here is an example producing the error:
    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).

14.12: Virtual constructors

In section14.2 we learned thatC++ supportsvirtualdestructors. Like many other object oriented languages (e.g.,Java),however, the notion of avirtual constructor is not supported. Not havingvirtual constructors becomes a liability when only base class references orpointers are available, and a copy of a derived class object isrequired.Gamma et al. (1995) discuss thePrototype design pattern to deal with thissituation.

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:

In this sequence, only theClonable 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    */




[8]ページ先頭

©2009-2025 Movatter.jp