Section 2 - Not So Simple Multiple Inheritance

10A.2.1 Not-So-Simple Multiple Inheritance

If each class, Base1,  Base2, is derived normally, from SuperProtoBase (i.e. as we have done in the past) as in:

class Base1 : public SuperProtoBase

or

class Base2 : private SuperProtoBase

then the picture is more accurately described like this:

SuperProtoBase      SuperProtoBase
\                  /
\                /
\              /
\            /
\          /
Base1    Base2
\         /
\       /
\     /
\   /
\ /
Derived

indicating that there are actually two copies of SuperProtoBase contained inside every Derived object, one belonging to Base1, and the other to Base2. Going back to our previous comments about parent classes that have members that are named the same, we must, from within the derived member functions or derived object, disambiguate using the scope resolution operator.

But what if we prefer to have only one copy of the SuperProtoBase members in each Derived object? We use the virtual derivation protocol:

class Base1 : virtual public SuperProtoBase
{
}

class Base2 : virtual private SuperProtoBase
{
}

Now, there is only one SuperProtoBase built in to every derived object, so no need to disambiguate.

The thing to remember is this: when deriving a class using the virtual keyword, nothing different happens at that level (i.e., in the class we are deriving). So the relationship between Base1 and SuperProtoBase does not change just because Base1 decided to derive itself virtually from SuperProtoBase. This is future planning, for the day when Base1 becomes a parent to some multiply derived class, a grandchild to SuperProtoBase.

Fourth (Very) Sticky Question: If Base1 and Base2 constructors result in different initializations to SuperProtoBase members, then does the left-to-right rule mean that the rightmost constructor persists? That is, if Base1's constructor does this:
Base1::Base1() : SuperProtoBase(3)
{
}
presumably storing 3 in one of SuperProtoBase's members, and Base2's constructor does this:
Base2::Base2() : SuperProtoBase(5)
{
}
presumably storing 5 in that same member, then would
Derived::Derived() : Base1(), Base2()
{
}
result in a 5 being passed into the SuperProtoBase constructor?

Answer: No. This seems logical, but it just doesn't work that way. Instead, C++ requires that you call the virtual base class constructor directly from the most derived class if you want to pass parameters to it.

Derived::Derived() : Base1(), Base2(),
SuperProtoBase(12);
{

Also, it doesn't matter if SuperProtoBase(12) is placed to the left or right of Base1(), Base2(). It would be the only function call affecting SuperProtoBase members from these constructor calls in the function header.

Fifth (Very) Sticky Question: (Actually, the first very, very sticky question). Does the statement about calling the virtual base class constructor from the most derived class mean that the intermediate classes, Base1 and Base2 can't change the virtual SuperProtoBase members at all?

Answer: No. That statement only applied to chaining the constructor calls, and the fact that the chain doesn't penetrate the virtual keyword. Inside the constructors you can manually set virtual base class members, and then, the rightmost function call will have the lasting effect on such members.

In the following examples I use the word struct in place of classstruct and class mean exactly the same thing, except that the default accessibility for struct is public, while in classes the default is private.

Compare these two programs.  In the first, all derived classes rely on function chaining to set the base class data.  Because of that, the "mid-level" classes, B and C, do not affect the final result of the base class data.  Only the lowest level child, D, affects that value, and does so explicitly in the constructor chain call to the grandparent class A.

struct A
{
    int x;
    A(int n) { x = n; }
};

struct B : virtual public A
{
    B(int n) : A(n) {}
};

struct C : virtual public A
{
    C(int n) : A(n) { }
};

struct D: public B, public C
{
    D(int n) : A(27), B(100), C(n){/* or "x = 27" here */}
    void show() { cout << x << "!\n";}
};

Produces:

conssole shot


 
But in the next example, one of the "mid-level" classes, C, does not rely on chaining to set the base class data, but does it explicitly with a -12.  Therefore, because the grandchild class constructor chains to C last (rightmost), that -12 will be the value that persists:

struct A
{
    int x;
    A(int n) { x = n; }
};

struct B : virtual public A
{
    B(int n) : A(n) {}
};

struct C : virtual public A
{
    C(int n) : A(n) { x = -12;}
};

struct D: public B, public C
{
    D(int n) : A(27), B(100), C(n){/* like "x = -12" here. (need x = 27 to overpower C) */}
    void show() { cout << x << "!\n";}
};


void main()
{
    D d(25);

    d.show();
}
conssole shot

As you can see, things can get pretty sticky in the land of multiple inheritance.