Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this diamond class inheritance output not what I expect?

Consider:

#include <iostream>

using namespace std;

class A {// base class
private:
    int data;
public:
    A(int data = 0) 
    {
        this->data = data;
    }
    void show() 
    {
        cout << data << endl;
        return;
    }
};

class B : virtual public A {
public:
    B(int data = 0) :
        A(data) {
    }
};

class C : virtual public A {
public:
    C(int data = 0) :
        A(data) {
    }
};

class D : public B, public C {
public:
    D(int dataB = 0, int dataC = 0) :
        B(dataB),
        C(dataC) {
    }
};

int main() {
    D d(1, 2);
    d.B::show();
    d.C::show();
    return 0;
}

The above code is the diamond class inheritance diagram. The base class is A. I use virtual inheritance to avoid the diamond problem. But why is the output of this program 0,0, not 1,2 as I expect?

B's constructor is passed data=1, and in its initializer list it calls A with data. C's constructor similar is passed data=2 and its initializer list it calls A with data.

We then ask the B and C subobjects to show their value. And we get 0 0 not 1 2 as I expect.

like image 690
hj w Avatar asked Apr 24 '17 06:04

hj w


2 Answers

When you have this scheme with virtual inheritance, it is up to the most derived class in the hierarchy (in this case D) to call the constructor of the common base (A)1,2.

Since your constructor for A has a default parameter data = 0, it can be used as a default constructor. And this is what's happening, the common A sub-object gets default constructed, since you omitted it from D's member initialization list.

If you remove the default value for data, you'll get a nice compiler error for emphasis:

A(int data) 
{
    this->data = data;
}

On g++ it results with:

main.cpp: In constructor 'D::D(int, int)':
main.cpp:37:16: error: no matching function for call to 'A::A()'
         C(dataC) {

1 Remember that with virtual inheritance there is only one sub-object of type A. And both the B and C sub-objects refer to it. It is impossible for your calls to show to print different things, since they access the same data. That is why it's up to the most derived class, so there is no ambiguity.

[class.mi/4]

A base class specifier that does not contain the keyword virtual specifies a non-virtual base class. A base class specifier that contains the keyword virtual specifies a virtual base class. For each distinct occurrence of a non-virtual base class in the class lattice of the most derived class, the most derived object shall contain a corresponding distinct base class subobject of that type. For each distinct base class that is specified virtual, the most derived object shall contain a single base class subobject of that type.

[class.base.init/13.1]

In a non-delegating constructor, initialization proceeds in the following order:

  • First, and only for the constructor of the most derived class, virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

  • ...

2 So if you want to construct A with specific data you'd define D::D() like this instead:

D(int dataA = 0) :
  A(dataA) {
}
like image 151
StoryTeller - Unslander Monica Avatar answered Nov 15 '22 16:11

StoryTeller - Unslander Monica


When you have virtual inheritance, the virtual base class is initialized by the constructor of the most derived class.

D(int dataB = 0, int dataC = 0) :
    B(dataB),
    C(dataC) {}

is equivalent to:

D(int dataB = 0, int dataC = 0) :
    A(),
    B(dataB),
    C(dataC) {}

which is, in your case, the same as

D(int dataB = 0, int dataC = 0) :
    A(0),
    B(dataB),
    C(dataC) {}

Unless you construct an instance of B,

B(int data = 0) :
    A(data) {
}

is the same as

B(int data = 0) {}

with no code to initialize A since A is already initialized in the constructor of D.

Same thing applies to the implementation of C::C(int data).

That explains the output you are seeing.

like image 31
R Sahu Avatar answered Nov 15 '22 17:11

R Sahu