2

I've started learning about virtual inheritance (and how it may solve problems of having a class derived from two parent classes with same parent). To better understand the mechanism behind it, i made a following example:

class A {
public: 
    A(string text = "Constructor A") { cout << text << endl; }
};

class B:  public A {
public:
    B(): A("A called from B") { cout << "Constructor B" << endl; }
};

class C : virtual public A {
public:
    C() : A("A called from C") { cout << "Constructor C" << endl; }
};

class D :  public B,  public C {
public: 
    D() { cout << "Constructor D" << endl; }
};

I have class A, class B derived from A, class C virtually derived from A, and class D derived from B and C. In main i just form an object of class D: D d; and i get the following output

Constructor A
A called from B
Constructor B
Constructor C
Constructor D

What bothers me is, why is there "Constructor A" signalizing that it is not called by either class B or C. And why is there not "A called from C" before "Constructor C". For the latter one i know it has to do with the class C being virtually derived so i guess it does not call Constructor A again since object from class A has already been formed (twice in fact).

EDIT:

In main i just make one object type D.

int main() {
  D d;
}
dzi
  • 319
  • 1
  • 13
  • 1
    Can you show us your `main()` where you create instances of these classes? – Hawky Jan 11 '20 at 18:34
  • 1
    @Hawky Sure, but like i said, i just make one object type D and that's it. – dzi Jan 11 '20 at 18:39
  • Sorry, I missed that line, my fault. – Hawky Jan 11 '20 at 18:45
  • 1
    If what you're trying to do is add a set of pre-existing features to a base class as concisely as possible, then also look into [mix-ins with templated inheritance](https://stackoverflow.com/questions/18773367/what-are-mixins-as-a-concept) as an alternative to virtual inheritance. Put briefly, you can replace `struct FooableBase : virtual Base {...}` with `template struct Fooable : BaseT {...}`, and then replace `struct FooBar : FooableBase, BarableBase {...}` with `struct FooBar : Fooable> {...}`. I find the template approach to be **much** easier to work with. – alter_igel Jan 11 '20 at 19:04
  • @alterigel This looks like a very clean way to do it. I will definitely have a look, thanks. – dzi Jan 11 '20 at 19:17

3 Answers3

4

Firstly, since B derives from A non-virtually, D ends up with two A subobjects (as if you didn't use virtual inheritance at all).

The non-virtual A is constructed by B() (hence "A called from B"), and the virtual one prints "Constructor A" when constructed.

That's because the constructors of virtual (possibly indirect) bases are always called by the constructor of the most derived class (D), rather than by the constructors of any intermediate bases (C).

It means that : A("A called from C") in C() is ignored (since you're not constructing a standalone C). And since D() doesn't mention A in its member initializer list, the virtual A is constructed without any parameters (as if by : A()).


Also, it's worth mentioning that all virtual bases are constructed before the non-virtual ones.

So your code is guaranteed to print Constructor A before A called from B.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • If i understood correctly, the initialization will begin from class C, which has been virtually derived from A, so a constructor with no parameters will be called from there. Then initialization will continue with X in a normal way (calling constructor A with parameter text and then with constructor X). Then initialization will move to Y and call its constructor, and then finally constructor of D will be called? – dzi Jan 11 '20 at 18:56
  • 1
    @dzi, what are `X` and `Y`? – Evg Jan 11 '20 at 18:58
  • @dzi No, initialization begins from the most derived class, `D`. It constructs all (possibly indirect) virtual bases (`A`), then all direct non-virtual bases (`B` (which in turn constructs the non-virtual `A`) and `C` (which doesn't construct any bases)). – HolyBlackCat Jan 11 '20 at 18:59
  • So virtual base will be constructed first, even if it is not directly the base of most derived class? – dzi Jan 11 '20 at 19:03
  • @Evg sorry for confusion i meant B and C in places of X and Y. – dzi Jan 11 '20 at 19:04
  • @HolyBlackCat That is the answer i have been looking for all along. I thought it means that it prefers construction of virtual class but in each definition independently – dzi Jan 11 '20 at 19:15
3

The problem

Your code forgot something and you still have two A objects in a D object. You can verify this claim by adding a public int test member to A and try the following code. You will get two different addresses:

D d;
cout << &d.B::test <<endl;   // the non virtual A subobject of B
cout << &d.C::test <<endl;   // the virtual A subobject of C

Online demo

The solution

All classes that share A virtually and that directly inherit from A must declare the virtual inheritance. So you need to correct class B:

class B:   virtual public A {...}  // you forgot the virtual here 

The code snippets above would work as expected, and you could even address d.test without getting any ambiguity error. Online demo

Edit: The delicate construction of A

The C++ rules require each object with a virtual A sub-object to provide a constructor for A. In your case, D should provide for the virual A's construction.

Since there is no explicit constructor, D will look for a default-construct A. And it finds one, since you provide a default argument for the A constructor. It's misleading because it tells you "A constructor" when in reality it was D that used it.

If you remove this default argument, your code won't compile anymore. You then need something like this to get A properly constructed:

class A {
public: 
    int test; 
    A(string text) { cout << "A is constructed: "<<text << endl; }
};

class D :  public B,  public C {
public: 
    D() : A("Mandatory, if there is no default consructor") { cout << "Constructor D" << endl; }
};

Online demo

Why are the C++ construction rules like that ? Because when you have virtual inheritance, there is no reason that the A construction by B is drawn over the construction by C . Nor the contrary. On the other side, both B and C define how to construct their A sub-object. To solve the ambiguity in the choice of the right construction, this rule was decided. And it may be painful if the virtual class doesn't have a default constructor.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • I was mostly aware of that. But what bugs me is "where is 'Constructor A' coming from", if it was called from class B or class C it would have said "A called from B/C", but it just says "Constructor A" like a constructor with no parameters was called. – dzi Jan 11 '20 at 18:43
  • 1
    @dzi Sorry, it's because of the default argument. See my edit – Christophe Jan 11 '20 at 18:53
  • Another reason C++ rules are like that is because virtual inheritance has an extra level of indirection. Following the rule of "you don't pay for what you don't use", C++ has an opt-in for "paying" for the extra overhead of virtual inheritance rather than it being the default. – Eljay Jan 11 '20 at 19:13
2

When a type has a virtual base, the virtual base gets constructed from the constructor of the most-derived type. So "Constructor A" is called from D's constructor.

A bit more detail:

#include <iostream>

struct base {
    base() { std::cout << "base()\n"; }
    base(int) { std::cout << "base(int)\n"; }
};

struct i1 : virtual base {
    i1() : base(0) { std::cout << "i1()\n"; }
};

struct i2 : virtual base {
    i2() : base(1) { std::cout << "i2()\n"; }
};

struct d : i1, i2 {
};

Now, if the code creates an object of type i1 the default constructor for i1 calls base(int), as written.

But when you create an object of type d, the constructor for d is responsible for constructing the base object. Since d does not have a default constructor, the compiler generates one that calls the default constructor for base before it calls the default constructors for i1 and i2.

int main() {
    d d_obj;
    return 0;
}

the output here is

[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base()
i1()
i2()
[temp]$ 

Note that the constructors for i1 and i2 did not construct the base subobject. The compiler took care fo that: the base should only be initialized once, and the constructor for d did that.

If you want a different initialization for the base object, write that in the constructor:

d::d() : base(2) {}

adding that to the class d produces this output:

[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base(int)
i1()
i2()
[temp]$ 
Pete Becker
  • 74,985
  • 8
  • 76
  • 165