16.3 VIRTUAL BASES FOR MULTIPLE INHERITANCE


16.3 VIRTUAL BASES FOR MULTIPLE INHERITANCE

By declaring a common base to be virtual, we eliminate the problem of duplicate construction of subobjects of the common-base type.

For the purpose of explaining the concept of a virtual base, consider the hierarchy shown in Figure 16.8. Let's say we construct this hierarchy by using the following

click to expand
Figure 16.8

      class derivations:      class Y : public X { ............ }      class T : public X { ............ }      class Z : public Y { ............ }      class U : public Z, public T { .........} 

Now if we construct an object of type U, we will have the subobjects shown in Figure 16.9 residing inside this object—clearly an undesirable situation. To forestall the duplication of subobjects of type X inside an object of type U, C++ requires us to declare X to be a virtual base of its immediate descendents, Y and T:

click to expand
Figure 16.9

      class Y : virtual public X {      //....      };      class T : virtual public X {      //....      }; 

Declaring X to be a virtual base would cause the X subobject to be shared by the Y and the T subobjects, resulting in the pictorial depiction shown in Figure 16.10. While declaring X as a virtual base of Y and T does result in the sharing of the X subobject, it requires that the constructor for X be now invoked explicitly in all the descendents of X. Ordinarily, a derived class constructor cannot invoke the constructor of its "indirect" base classes. (A base class is an indirect base if it is not an immediate base class.) So, ordinarily, the constructor for, say, the Z class is not allowed to invoke the constructor for the class X. But, with X as the virtual base, we must now explicitly call the constructor of X in the constructors of Y, Z, U, and T.

click to expand
Figure 16.10

The program shown below constitutes an implementation of the hierarchy of Figure 16.8. Lines (A) and (B) declare X to be a virtual base of the classes Y and T. Note how that fact affects the syntax of the constructors in the lines (C) and (D) for the classes Z and U, respectively.

 
//VirtualBase.cc #include <iostream> using namespace std; class X { int x; public: X( int xx ) : x(xx) {} virtual void print() { cout << "printing value of x of X subobject: " << x << endl; } }; class Y : virtual public X { //(A) int y; public: Y( int xx, int yy ) : X( xx ), y( yy ) {} void print() { X::print(); cout << "printing value of y of Y subobject: " << y << endl; } }; class T : virtual public X { //(B) int t; public: T( int xx, int tt ) : X( xx ), t( tt ) {} void print(){ X::print(); cout << "printing value of t of T subobject: " << t << endl; } }; class Z : public Y { int z; public: Z( int xx, int yy, int zz ) : Y( xx, yy ), X(xx), z( zz ) {} //(C) void print() { Y::print(); cout << "printing value of z of Z subobject: " << z << endl; } }; class U : public Z, public T { int u; public: U( int xx, int yy, int zz, int tt, int uu ) : Z( xx, yy, zz ), T( xx, tt ), X( xx ), u( uu ) {} //(D) void print() { Z::print(); T::print(); cout << "printing value of u of U subobject: " << u << endl; } }; int main() { cout << "X object coming up: " << endl; X xobj( 1 ); xobj.print(); //(E) cout << endl; cout << "Y object coming up: " << endl; Y yobj( 11, 12 ); yobj.print(); //(F) cout << endl; cout << "Z object coming up: " << endl; Z zobj( 110, 120, 130 ); zobj.print(); //(G) cout << endl; cout << "T object coming up: " << endl; T tobj( 21, 22 ); tobj.print(); //(H) cout << endl; cout << "U object coming up: " << endl; U uobj(9100, 9200, 9300, 9400, 9500 ); //(I) uobj.print(); cout << endl; return 0; }

If X were not a virtual base in the hierarchy of Figure 16.8, the constructor for U in line (D) would look like

      U( int xx, int yy, int zz, int tt, int uu )          : Z( xx, yy, zz ), T( xx, tt ), u( uu ) {} 

But, because X is a virtual base, the constructor for U must include a direct invocation of the virtual base constructor:

      U( int xx, int yy, int zz, int tt, int uu )          : Z( xx, yy, zz ), T( xx, tt ), X( xx ), u( uu ) {} 

Note the appearance of X(xx) in the member initialization syntax. As shown in line (C), a similar modification has to be made to the constructor for Z.

The program produces the following output:

 X object coming up:                           // output of line (E) printing value of x of X subobject: 1 Y object coming up:                           // output of line (F) printing value of x of X subobject: 11 printing value of y of Y subobject: 12 Z object coming up:                           // output of line (G) printing value of x of X subobject: 110 printing value of y of Y subobject: 120 printing value of z of Z subobject: 130 T object coming up:                           // output of line (H) printing value of x of X subobject: 21 printing value of t of T subobject: 22 U object coming up:                           // output of line (I) printing value of x of X subobject: 9100 printing value of y of Y subobject: 9200 printing value of z of Z subobject: 9300 printing value of x of X subobject: 9100 printing value of t of T subobject: 9400 printing value of u of U subobject: 9500 

This bring us to an important question about the program shown above: Considering that every descendent of X has a direct call to the constructor for X, whose job is it to create the the shared X subobject? The answer to this question is supplied by the following specification in the C++ language: It is the job of the "most-derived" object to construct a virtual-base subobject.

So, when we construct an object of type U, the job of constructing the shared X subobject falls on U's constructor's call to the X's constructor. To verify this fact, suppose we change the definition of the U class in the previous program to

      class U : public Z, public T {          int u;      public:          U ( int xx, int yy, int zz, int tt, int uu )          : W(4444, yy, zz), T(5555, tt), X( 6666 ), u( uu ){}      //(J)          void print() {               W::print();               T::print();               cout << "printing value of u of U subobject:" << u << endl;          }      }; 

Note how we supply different arguments for the different calls to the X's constructor in line (J). For the direct invocation of X's constructor, we supply a value of 6666 for the data member of the X subobject. But, for the calls to the X's constructor through W, we supply a value of 4444 for the data-member of the X subobject, and so on.

When the above code is substituted for the definition of class U in the program VirtualBase.cc, we get the following output:

 X object coming up:                          // output of line (E) printing value of x of X subobject: 1 Y object coming up:                          // output of line (F) printing value of x of X subobject: 11 printing value of y of Y subobject: 12 Z object coming up:                          // output of line (G) printing value of x of X subobject: 110 printing value of y of Y subobject: 120 printing value of z of Z subobject: 130 T object coming up:                          // output of line (H) printing value of x of X subobject: 21 printing value of t of T subobject: 22 U object coming up:                          // output of line (I) printing value of x of X subobject: 6666                         //(K) printing value of y of Y subobject: 9200                         //(L) printing value of z of Z subobject: 9300                         //(M) printing value of x of X subobject: 6666                         //(N) printing value of t of T subobject: 9400                         //(O) printing value of u of U subobject: 9500                         //(P) 

The printout for the U object constructed by the statement in line (I) of main(), via the new constructor shown in line (J) of the modified definition of the U class, is shown in lines (K) through (P) above. As is clear from the output lines (K) and (N), the X subobject corresponds to the constructor invocation

      X( 6666 ) 

in line (J) of the modified U class above. This invocation is U's attempt at direct creation of the X subobject.

The "most-derived" rule for the construction of the common-base subobject applies even to classes that are on single inheritance paths in a hierarchy with a virtual base. For example, when we construct an object of type Z—even though Z is on a single inheritance path, but it is a path that has a virtual base in it—the job of creating the X subobject falls on Z's constructor making a direct call to the X's constructor.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

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