11

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
hj w
  • 155
  • 9
  • 1
    You can have 1,2 very easily -just make that inheritance NOT virtual :) Then you'll have TWO copies of A. – Francesco Dondi Apr 24 '17 at 08:17
  • Anyway the `this->` is odd. In modern C++ you can write `A(int data = 0): data(data) {}`, 'constructor initialization list', which is cleaner, and allows you to have constant members. – Francesco Dondi Apr 24 '17 at 08:22
  • 1
    This isn't the problem, but do you really need the extra stuff that `std::endl` does? `'\n'` ends a line. – Pete Becker Apr 24 '17 at 12:29
  • 1
    [C++: “std::endl” vs “\n”](http://stackoverflow.com/q/213907/995714) – phuclv Apr 24 '17 at 13:26

3 Answers3

21

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) {
}
Community
  • 1
  • 1
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
7

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.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

I think you misunderstood the concept of virtual inheritance and the 'diamond problem'. With virtual inheritance, you create a diamond inheritance pattern, but from your post, it appears that you want to avoid that and instead have two bases A, one from B and another from C. To obtain that, simply avoid virtual inheritance in B and C, when your code will write 1 2.

Incidently, if only B has virtual inheritance from A but C conventional inheritance from A, then D will have two bases A, but that from B is again default initialised (as explained in the other answers).

Walter
  • 44,150
  • 20
  • 113
  • 196