Section 17.3. Multiple and Virtual Inheritance


17.3. Multiple and Virtual Inheritance

Most C++ applications use public inheritance from a single base class. In some cases, however, single inheritance is inadequate, either because it fails to model the problem domain or the model it imposes is unnecessarily complex.

In these cases, multiple inheritance may model the application more directly. Multiple inheritance is the ability to derive a class from more than one immediate base class. A multiply derived class inherits the properties of all its parents. Although simple in concept, the details of intertwining multiple base classes can present tricky design-level and implementation-level problems.

17.3.1. Multiple Inheritance

This section uses a pedagogical example of a zoo animal hierarchy. Our zoo animals exist at different levels of abstraction. There are the individual animals, distinguished by their names, such as Ling-ling, Mowgli, and Balou. Each animal belongs to a species; Ling-Ling, for example, is a giant panda. Species, in turn, are members of families. A giant panda is a member of the bear family. Each family, in turn, is a member of the animal kingdomin this case, the more limited kingdom of a particular zoo.

Each level of abstraction contains data and operations that support a wider category of users. We'll define an abstract ZooAnimal class to hold information that is common to all the zoo animals and provides the public interface. The Bear class will contain information that is unique to the Bear family, and so on.

In addition to the actual zoo-animal classes, there are auxiliary classes that encapsulate various abstractions such as endangered animals. In our implementation of a Panda class, for example, a Panda is multiply derived from Bear and Endangered.

Defining Multiple Classes

To support multiple inheritance, the derivation list

     class Bear : public ZooAnimal {     }; 

is extended to support a comma-separated list of base classes:

     class Panda : public Bear, public Endangered {     }; 

The derived class specifies (either explicitly or implicitly) the access level public, protected, or private for each of its base classes. As with single inheritance, a class may be used as a base class under multiple inheritance only after it has been defined. There is no language-imposed limit on the number of base classes from which a class can be derived. A base class may appear only once in a given derivation list.

Multiply Derived Classes Inherit State from Each Base Class

Under multiple inheritance, objects of a derived class contain a base-class subobject (Section 15.2.3, p. 565) for each of its base classes. When we write

 Panda ying_yang("ying_yang"); 

the object ying_yang is composed of a Bear class subobject (which itself contains a ZooAnimal base-class subobject), an Endangered class subobject, and the nonstatic data members, if any, declared within the Panda class (see Figure 17.2).

Figure 17.2. Multiple Inheritance Panda Hierarchy


Derived Constructors Initialize All Base Classes

Constructing an object of derived type involves constructing and initializing all its base subobjects. As is the case for inheriting from a single base class (Section 15.4.1, p. 580), derived constructors may pass values to zero or more of their base classes in the constructor initializer:

     // explicitly initialize both base classes     Panda::Panda(std::string name, bool onExhibit)           : Bear(name, onExhibit, "Panda"),             Endangered(Endangered::critical) { }     // implicitly use Bear default constructor to initialize the Bear subobject     Panda::Panda()           : Endangered(Endangered::critical) { } 

Order of Construction

The constructor initializer controls only the values that are used to initialize the base classes, not the order in which the base classes are constructed. The base-class constructors are invoked in the order in which they appear in the class derivation list. For Panda, the order of base-class initialization is:

  1. ZooAnimal, the ultimate base class up the hierarchy from Panda's immediate base class Bear

  2. Bear, the first immediate base class

  3. Endangered, the second immediate base, which itself has no base class

  4. Panda; the members of Panda itself are initialized, and then the body of its constructor is run.

The order of constructor invocation is not affected by whether the base class appears in the constructor initializer list or the order in which base classes appear within that list.



For example, in Panda's default constructor, the Bear default constructor is invoked implicitly; it does not appear in the constructor initializer list. Even so, Bear's default constructor is invoked prior to the explicitly listed constructor of Endangered.

Order of Destruction

Destructors are always invoked in the reverse order from which the constructors are run. In our example, the order in which the destructors are called is ~Panda, ~Endangered, ~Bear, ~ZooAnimal.

Exercises Section 17.3.1

Exercise 17.23:

Which, if any, of the following declarations are in error. Explain why.

     (a) class CADVehicle : public CAD, Vehicle { ... };     (b) class DoublyLinkedList:               public List, public List { ... };     (c) class iostream: public istream, public ostream { ... }; 

Exercise 17.24:

Given the following class hierarchy, in which each class defines a default constructor,

     class A { ... };     class B : public A { ... };     class C : public B { ... };     class X { ... };     class Y { ... };     class Z : public X, public Y { ... };     class MI : public C, public Z { ... }; 

what is the order of constructor execution for the following definition?

     MI mi; 


17.3.2. Conversions and Multiple Base Classes

Under single inheritance, a pointer or a reference to a derived class can be converted automatically to a pointer or a reference to a base class. The same holds true with multiple inheritance. A pointer or reference to a derived class can be converted to a pointer or reference to any of its base classes. For example, a Panda pointer or reference could be converted to a pointer or a reference to ZooAnimal, Bear, or Endangered:

     // operations that take references to base classes of type Panda     void print(const Bear&);     void highlight(const Endangered&);     ostream& operator<<(ostream&, const ZooAnimal&);     Panda ying_yang("ying_yang");    // create a Panda object     print(ying_yang);      // passes Panda as reference to Bear     highlight(ying_yang);  // passes Panda as reference to Endangered     cout << ying_yang << endl;  // passes Panda as reference to ZooAnimal 

Under multiple inheritance, there is a greater possibility of encountering an ambiguous conversion. The compiler makes no attempt to distinguish between base classes in terms of a derived-class conversion. Converting to each base class is equally good. For example, if there was an overloaded version of print

     void print(const Bear&);     void print(const Endangered&); 

an unqualified invocation of print with a Panda object

     Panda ying_yang("ying_yang");     print(ying_yang);              // error: ambiguous 

results in a compile-time error that the call is ambiguous.

Exercises Section 17.3.2

Exercise 17.25:

Given the following class hierarchy, in which each class defines a default constructor,

     class X { ... };     class A { ... };     class B : public A { ... };     class C : private B { ... };     class D : public X, public C { ... }; 

which, if any, of the following conversions are not permitted?

     D *pd = new D;     (a) X *px = pd; (c) B *pb = pd;     (b) A *pa = pd; (d) C *pc = pd; 


Virtual Functions under Multiple Inheritance

To see how the virtual function mechanism is affected by multiple inheritance, let's assume that our classes define the virtual members listed in Table 17.2.

Table 17.2. Virtual Function in the ZooAnimal/Endangered Classes

Function

Class Defining Own Version

print

ZooAnimal::ZooAnimal

 

Bear::Bear

 

Endangered::Endangered

 

Panda::Panda

highlight

Endangered::Endangered

 

Panda::Panda

toes

Bear::Bear

 

Panda::Panda

cuddle

Panda::Panda

destructor

ZooAnimal::ZooAnimal

 

Endangered::Endangered


Lookup Based on Type of Pointer or Reference

As with single inheritance, a pointer or reference to a base class can be used to access only members defined (or inherited) in the base. It cannot access members introduced in the derived class.

When a class inherits from multiple base classes, there is no implied relationship between those base classes. Using a pointer to one base does not allow access to members of another base.

As an example, we could use a pointer or reference to a Bear, ZooAnimal, Endangered, or Panda to access a Panda object. The type of the pointer we use determines which operations are accessible. If we use a ZooAnimal pointer, only the operations defined in that class are usable. The Bear-specific, Panda-specific, and Endangered portions of the Panda interface are inaccessible. Similarly, a Bear pointer or reference knows only about the Bear and ZooAnimal members; an Endangered pointer or reference is limited to the Endangered members:

     Bear *pb = new Panda("ying_yang");     pb->print(cout); // ok: Panda::print(ostream&)     pb->cuddle();    // error: not part of Bear interface     pb->highlight(); // error: not part of Bear interface     delete pb;       // ok: Panda::~Panda() 

If the Panda object had been assigned to a ZooAnimal pointer, this set of calls would resolve exactly the same way.

When a Panda is used via an Endangered pointer or reference, the Panda-specific and Bear portions of the Panda interface are inaccessible:

     Endangered *pe = new Panda("ying_yang");     pe->print(cout);  // ok: Panda::print(ostream&)     pe->toes();       // error: not part of Endangered interface     pe->cuddle();     // error: not part of Endangered interface     pe->highlight();  // ok: Endangered::highlight()     delete pe;        // ok: Panda::~Panda() 

Determining Which Virtual Destructor to Use

Assuming all the root base classes properly define their destructors as virtual, then the handling of the virtual destructor is consistent regardless of the pointer type through which we delete the object:

     // each pointer points to a Panda     delete pz; // pz is a ZooAnimal*     delete pb; // pb is a Bear*     delete pp; // pp is a Panda*     delete pe; // pe is a Endangered* 

Assuming each of these pointers points to a Panda object, the exact same order of destructor invocations occurs in each case. The order of destructor invocations is the reverse of the constructor order: The Panda destructor is invoked through the virtual mechanism. Following execution of the Panda destructor, the Endangered, Bear, then ZooAnimal destructors are invoked in turn.

Exercises Section 17.3.2

Exercise 17.26:

On page 735 we presented a series of calls made through a Bear pointer that pointed to a Panda object. We noted that if the pointer had been a ZooAnimal pointer the calls would resolve the same way. Explain why.

Exercise 17.27:

Assume we have two base classes, Base1 and Base2, each of which defines a virtual member named print and a virtual destructor. From these base classes we derive the following classes each of which redefines the print function:

     class D1 : public Base1 { /* ... */ };     class D2 : public Base2 { /* ... */ };     class MI : public D1, public D2 { /* ... */ }; 

Using the following pointers determine which function is used in each call:

     Base1 *pb1 = new MI; Base2 *pb2 = new MI;     D1 *pd1 = new MI; D2 *pd2 = new MI;     (a) pb1->print(); (b) pd1->print(); (c) pd2->print();     (d) delete pb2;   (e) delete pd1;   (f) delete pd2; 

Exercise 17.28:

Write the class definitions that correspond to Table 17.2 (p. 735).


17.3.3. Copy Control for Multiply Derived Classes

The memberwise initialization, assignment and destruction (Chapter 13) of a multiply derived class behaves in the same way as under single inheritance. Each base class is implicitly constructed, assigned or destroyed, using that base class' own copy constructor, assignment operator or destructor.

Let's assume that Panda uses the default copy control members. Using the default copy constructor, the initialization of ling_ling

     Panda ying_yang("ying_yang");  // create a Panda object     Panda ling_ling = ying_yang;   // uses copy constructor 

invokes the Bear copy constructor, which in turn runs the ZooAnimal copy constructor prior to executing the Bear copy constructor. Once the Bear portion of ling_ling is constructed, the Endangered copy constructor is run to create that part of the object. Finally, the Panda copy constructor is run.

The synthesized assignment operator behaves similarly to the copy constructor. It assigns the Bear (and through Bear, the ZooAnimal) parts of the object first. Next, it assigns the Endangered part, and finally the Panda part.

The synthesized destructor destroys each member of the Panda object and calls the destructors for the base class parts, in reverse order from construction.

As is the case for single inheritance (Section 15.4.3, p. 584), if a class with multiple bases defines its own destructor, that destructor is responsible only for cleaning up the derived class. If the derived class defines its own copy constructor or assignment operator, then the class is responsible for copying (assigning) all the base class subparts. The base parts are automatically copied or assigned only if the derived class uses the synthesized versions of these members.



17.3.4. Class Scope under Multiple Inheritance

Class scope (Section 15.5, p. 590) is more complicated in multiple inheritance because a derived scope may be enclosed by multiple base class scopes. As usual, name lookup for a name used in a member function starts in the function itself. If the name is not found locally, then lookup continues in the member's class and then searches each base class in turn. Under multiple inheritance, the search simultaneously examines all the base-class inheritance subtreesin our example, both the Endangered and the Bear/ZooAnimal subtrees are examined in parallel. If the name is found in more than one subtree, then the use of that name must explicitly specify which base class to use. Otherwise, the use of the name is ambiguous.

When a class has multiple base classes, name lookup happens simultaneously through all the immediate base classes. It is possible for a multiply derived class to inherit a member with the same name from two or more base classes. Unqualified uses of that name are ambiguous.



Multiple Base Classes Can Lead to Ambiguities

Assume both Bear and Endangered define a member named print. If Panda does not define that member, then a statement such as the following

     ying_yang.print(cout); 

results in a compile-time error.

The derivation of Panda, which results in Panda having two members named print, is perfectly legal. The derivation results in only a potential ambiguity. That ambiguity is avoided if no Panda object ever calls print. The error would also be avoided if each call to print specifically indicated which version of print was wantedBear::print or Endangered::print. An error is issued only if there is an ambiguous attempt to use the member.

If a declaration is found only in one base-class subtree, then the identifier is resolved and the lookup algorithm concludes. For example, class Endangered might have an operation to return the given estimated population of its object. If so, the following call

     ying_yang.population(); 

would compile without complaint. The name population would be found in the Endangered base class and does not appear in Bear or any of its base classes.

Name Lookup Happens First

Although the ambiguity of the two inherited print members is reasonably obvious, it might be more surprising to learn that an error would be generated even if the two inherited functions had different parameter lists. Similarly, it would be an error even if the print function were private in one class and public or protected in the other. Finally, if print were defined in ZooAnimal and not Bear, the call would still be in error.

As always, name lookup happens in two steps (Section 7.8.1, p. 268): First the compiler finds a matching declaration (or, in this case, two matching declarations, which causes the ambiguity). Only then does the compiler decide whether the declaration it found is legal.

Avoiding User-Level Ambiguities

We could resolve the print ambiguity by specifying which class to use:

     ying_yang.Endangered::print(cout); 

The best way to avoid potential ambiguities is to define a version of the function in the derived class that resolves the ambiguity. For example, we should give our Panda class a print function that chooses which version of print to use:

     std::ostream& Panda::print(std::ostream &os) const     {         Bear::print(os);        // print the Bear part         Endangered::print(os);  // print the Endangered part         return os;     } 

Code for Exercises to Section 17.3.4

    struct Base1 {        void print(int) const;     protected:        int    ival;        double dval;        char   cval;     private:         int   *id;     };     struct Base2 {         void print(double) const;     protected:         double fval;     private:         double dval;     };     struct Derived : public Base1 {         void print(std::string) const;     protected:         std::string sval;         double      dval;     };     struct MI : public Derived, public Base2 {         void print(std::vector<double>);     protected:         int                  *ival;         std::vector<double>  dvec;     }; 


Exercises Section 17.3.4

Exercise 17.29:

Given the class hierarchy in the box on this page and the following MI::foo member function skeleton,

     int ival;     double dval;     void MI::foo(double dval) { int id; /* ... */ } 

  1. identify the member names visible from within MI. Are there any names visible from more than one base class?

  2. identify the set of members visible from within MI::foo.

Exercise 17.30:

Given the hierarchy in the box on page 739, why is this call to print an error?

     MI mi;     mi.print(42); 

Revise MI to allow this call to print to compile and execute correctly.

Exercise 17.31:

Using the class hierarchy in the box on page 739, identify which of the following assignments, if any, are in error:

     void MI::bar() {         int sval;         // exercise questions occur here ...     }     (a) dval = 3.14159;   (d) fval = 0;     (b) cval = 'a';       (e) sval = *ival; (c) id = 1; 

Exercise 17.32:

Using the class hierarchy defined in the box on page 739 and the following skeleton of the MI::foobar member function

     void MI::foobar(double cval)     {         int dval;         // exercise questions occur here ...     } 

  1. assign to the local instance of dval the sum of the dval member of Base1 and the dval member of Derived.

  2. assign the value of the last element in MI::dvec to Base2::fval.

  3. assign cval from Base1 to the first character in sval from Derived.


17.3.5. Virtual Inheritance

Under multiple inheritance, a base class can occur multiple times in the derivation hierarchy. In fact, our programs have already used a class that inherits from the same base class more than once through its inheritance hierarchy.

Each of the IO library classes inherits from a common abstract base class. That abstract class manages the condition state of the stream and holds the buffer that the stream reads or writes. The istream and ostream classes inherit directly from this common base class. The library defines another class, named iostream, that inherits from both istream and ostream. The iostream class can both read and write a stream. A simplified version of the IO inheritance hierarchy is illustrated in Figure 17.3 on the facing page.

Figure 17.3. Virtual Inheritance iostream Hierarchy (Simplified)


As we know, a multiply inherited class inherits state and action from each of its parents. If the IO types used normal inheritance, then each iostream object would contain two ios subobjects: one instance contained within its istream subobject and the other within its ostream subobject. From a design perspective, this implementation is just wrong: The iostream class wants to read to and write from a single buffer; it wants the condition state to be shared across input and output operations. If there are two separate ios objects, this sharing is not possible.

In C++ we solve this kind of problem by using virtual inheritance. Virtual inheritance is a mechanism whereby a class specifies that it is willing to share the state of its virtual base class. Under virtual inheritance, only one, shared base-class subobject is inherited for a given virtual base regardless of how many times the class occurs as a virtual base within the derivation hierarchy. The shared base-class subobject is called a virtual base class.

The istream and ostream classes inherit virtually from their base class. By making their base class virtual, istream and ostream specify that if some other class, such as iostream, inherits from both of them, then only one copy of their common base class will be present in the derived class. We make a base class virtual by including the keyword virtual in the derivation list:

     class istream : public virtual ios { ... };     class ostream : virtual public ios { ... };     // iostream inherits only one copy of its ios base class     class iostream: public istream, public ostream { ... }; 

A Different Panda Class

For the purposes of illustrating virtual inheritance, we'll continue to use the Panda class as a pedagogical example. Within zoological circles, for more than 100 years there has been an occasionally fierce debate as to whether the Panda belongs to the Raccoon or the Bear family. Because software design is primarily a service industry, our most practical solution is to derive Panda from both:

     class Panda : public Bear,                   public Raccoon, public Endangered {     }; 

Our virtual inheritance Panda hierarchy is pictured in Figure 17.4. If we examine that hierarchy, we notice a nonintuitive aspect of virtual inheritance: The virtual derivation (in our case, of Bear and Raccoon) has to be made prior to any actual need for it to be present. Virtual inheritance becomes necessary only with the declaration of Panda, but if Bear and Raccoon are not already virtually derived, the designer of the Panda class is out of luck.

Figure 17.4. Virtual Inheritance Panda Hierarchy


In practice, the requirement that an intermediate base class specify its inheritance as virtual rarely causes any problems. Ordinarily, a class hierarachy that uses virtual inheritance is designed at one time by either one individual or a project design group. It is exceedingly rare for a class to be developed independently that needs a virtual base in one of its base classes and in which the developer of the new base class cannot change the existing hierarchy.

17.3.6. Virtual Base Class Declaration

A base class is specified as being derived through virtual inheritance by modifying its declaration with the keyword virtual. For example, the following declarations make ZooAnimal a virtual base class of both Bear and Raccoon:

     // the order of the keywords public and virtual is not significant     class Raccoon : public virtual ZooAnimal { /* ... */ };     class Bear : virtual public ZooAnimal { /* ... */ }; 

Specifying virtual derivation has an impact only in classes derived from the class that specifies a virtual base. Rather than affecting objects of the derived class' own type, it is a statement about the derived class' relationship to its own, future derived class.



The virtual specifier states a willingness to share a single instance of the named base class within a subsequently derived class.

Any class that can be specified as a base class also could be specified as a virtual base class. A virtual base may contain any class element normally supported by a nonvirtual base class.

Normal Conversions to Base Are Supported

An object of the derived class can be manipulated as usual through a pointer or a reference to a base-class type even though the base class is virtual. For example, all of the following Panda base class conversions execute correctly even though Panda inherits its ZooAnimal part as a virtual base:

     void dance(const Bear*);     void rummage(const Raccoon*);     ostream& operator<<(ostream&, const ZooAnimal&);     Panda ying_yang;     dance(&ying_yang);   // ok: converts address to pointer to Bear     rummage(&ying_yang); // ok: converts address to pointer to Raccoon     cout << ying_yang;   // ok: passes ying_yang as a ZooAnimal 

Visibility of Virtual Base-Class Members

Multiple-inheritance hierarchies using virtual bases pose fewer ambiguity problems than do those without virtual inheritance.

Members in the shared virtual base can be accessed unambiguously and directly. Similarly, if a member from the virtual base is redefined along only one derivation path, then that redefined member can be accessed directly. Under a nonvirtual derivation, both kinds of access would be ambiguous.



Assume a member named X is inherited through more than one derivation path. There are three possibilities:

  1. If in each path X represents the same virtual base class member, then there is no ambiguity because a single instance of the member is shared.

  2. If in one path X is a member of the virtual base class member and in another path X is a member of a subsequently derived class, there is also no ambiguitythe specialized derived class instance is given precedence over the shared virtual base class instance.

  3. If along each inheritance path X represents a different member of a subsequently derived class, then the direct access of the member is ambiguous.

As in a nonvirtual multiple inheritance hierarchy, ambiguities of this sort are best resolved by the class providing an overriding instance in the derived class.

Exercises Section 17.3.6

Exercise 17.33:

Given the following class hierarchy, which inherited members can be accessed without qualification from within the VMI class? Which require qualification? Explain your reasoning.

     class Base {     public:         bar(int);     protected:         int ival;     };     class Derived1 : virtual public Base {     public:         bar(char);         foo(char);     protected:         char cval;     };     class Derived2 : virtual public Base {     public:         foo(int);     protected:         int ival;         char cval;     };     class VMI : public Derived1, public Derived2 { }; 


17.3.7. Special Initialization Semantics

Ordinarily each class initializes only its own direct base class(es). This initialization strategy fails when applied to a virtual base class. If the normal rules were used, then the virtual base might be initialized multiple times. The class would be initialized along each inheritance path that contains the virtual base. In our ZooAnimal example, using normal initialization rules would result in both Bear and Raccoon attempting to initialize the ZooAnimal part of a Panda object.

To solve this duplicate-initialization problem, classes that inherit from a class that has a virtual base have special handling for initialization. In a virtual derivation, the virtual base is initialized by the most derived constructor. In our example, when we create a Panda object, the Panda constructor alone controls how the ZooAnimal base class is initialized.

Although the virtual base is initialized by the most derived class, any classes that inherit immediately or indirectly from the virtual base usually also have to provide their own initializers for that base. As long as we can create independent objects of a type derived from a virtual base, that class must initialize its virtual base. These initializers are used only when we create objects of the intermediate type.

In our hierarchy, we could have objects of type Bear, Raccoon, or Panda. When a Panda is created, it is the most derived type and controls initialization of the shared ZooAnimal base. When a Bear (or a Raccoon) is created, there is no further derived type involved. In this case, the Bear (or Raccoon) constructors directly initialize their ZooAnimal base as usual:

     Bear::Bear(std::string name, bool onExhibit):              ZooAnimal(name, onExhibit, "Bear") { }     Raccoon::Raccoon(std::string name, bool onExhibit)            : ZooAnimal(name, onExhibit, "Raccoon") { } 

The Panda constructor also initializes the ZooAnimal base, even though it is not an immediate base class:

     Panda::Panda(std::string name, bool onExhibit)           : ZooAnimal(name, onExhibit, "Panda"),             Bear(name, onExhibit),             Raccoon(name, onExhibit),             Endangered(Endangered::critical),             sleeping_flag(false) { } 

When a Panda is created, it is this constructor that initializes the ZooAnimal part of the Panda object.

How a Virtually Inherited Object Is Constructed

Let's look at how objects under virtual inheritance are constructed.

 Bear winnie("pooh");    // Bear constructor initializes ZooAnimal Raccoon meeko("meeko"); // Raccoon constructor initializes ZooAnimal Panda yolo("yolo");     // Panda constructor initializes ZooAnimal 

When a Panda object is created,

  1. The ZooAnimal part is constructed first, using the initializers specified in the Panda constructor initializer list.

  2. Next, the Bear part is constructed. The initializers for ZooAnimal Bear's constructor initializer list are ignored.

  3. Then the Raccoon part is constructed, again ignoring the ZooAnimal initializers.

  4. Finally, the Panda part is constructed.

If the Panda constructor does not explicitly initialize the ZooAnimal base class, then the ZooAnimal default constructor is used. If ZooAnimal doesn't have a default constructor, then the code is in error. The compiler will issue an error message when the definition of Panda's constructor is compiled.

Constructor and Destructor Order

Virtual base classes are always constructed prior to nonvirtual base classes regardless of where they appear in the inheritance hierarchy.



For example, in the following whimsical TeddyBear derivation, there are two virtual base classes: the ToyAnimal base class and the indirect ZooAnimal base class from which Bear is derived:

    class Character { /* ... */ };    class BookCharacter : public Character { /* ... */ };    class ToyAnimal { /* ... */ };    class TeddyBear : public BookCharacter,                      public Bear, public virtual ToyAnimal                      { /* ... */ }; 

Figure 17.5. Virtual Inheritance TeddyBear Hierarchy


The immediate base classes are examined in declaration order to determine whether there are any virtual base classes. In our example, the inheritance subtree of BookCharacter is examined first, then that of Bear, and finally that of ToyAnimal. Each subtree is examined starting at the root class down to the most derived class.

The order in which the virtual base classes are constructed for TeddyBear is ZooAnimal followed by ToyAnimal. Once the virtual base classes are constructed, the nonvirtual base-class constructors are invoked in declaration order: BookCharacter, which causes the Character constructor to be invoked, and then Bear. Thus, to create a TeddyBear, the constructors are invoked in the following order:

     ZooAnimal();           // Bear's virtual base class     ToyAnimal();           // immediate virtual base class     Character();           // BookCharacter's nonvirtual base class     BookCharacter();       // immediate nonvirtual base class     Bear();                // immediate nonvirtual base class     TeddyBear();           // most derived class 

where the initializers used for ZooAnimal and ToyAnimal are specified by the most derived class TeddyBear.

The same construction order is used in the synthesized copy constructor; the base classes also are assigned in this order in the synthesized assignment operator. The order of base-class destructor calls is guaranteed to be the reverse order of constructor invocation.

Exercises Section 17.3.7

Exercise 17.34:

There is one case in which a derived class need not supply initializers for its virtual base class(es). What is this case?

Exercise 17.35:

Given the following class hierarchy,

     class Class { ... };     class Base : public Class { ... };     class Derived1 : virtual public Base { ... };     class Derived2 : virtual public Base { ... };     class MI : public Derived1,                public Derived2 { ... };     class Final : public MI, public Class { ... }; 

  1. What is the order of constructor and destructor for the definition of a Final object?

  2. How many Base subobjects are in a Final object? How many Class subobjects?

  3. Which of the following assignments is a compile-time error?

     Base     *pb;      Class     *pc;     MI       *pmi;     Derived2  *pd2;     (a) pb = new Class;           (c) pmi = pb;     (b) pc = new Final;           (d) pd2 = pmi; 

Exercise 17.36:

Given the previous hierarchy, and assuming that Base defines the following three constructors, define the classes that inherit from Base, giving each class the same three constructors. Each constructor should use its argument to initialize its Base part.

     struct Base {         Base();         Base(std::string);         Base(const Base&);     protected:         std::string name;     }; 




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net