I have the following c++ code (VS2013 if that matters):
#include <iostream>
using namespace std;
struct A{
A() { cout << "A()" << endl; }
A(int i) { cout << "A(" << i << ")" << endl; }
};
struct B : /*virtual*/ public A{
B(int i) : A(i){ cout << "B(" << i << ")" << endl; }
};
struct C : /*virtual*/ public A{
C(int i) : A(i){ cout << "C(" << i << ")" << endl; }
};
struct D : /*virtual*/ public B, /*virtual*/ public C{
D() : B(2), C(3){ cout << "D()" << endl; }
};
void main() {
D d;
system("pause");
}
The code implements a diamond-shaped inheritance, with 4 inheritances in total: B:A, C:A, D:B, D:C
With none of these inheritances set to virtual
, I get the following output:
A(2) B(2) A(3) C(3) D()
That makes sense. D() first calls B(int x) which first calls A(int x), then calls C(int x) which first calls A(int x), and finally D() itself runs.
But as far as virtual inheritances, I have 4 inheritances, which means that I have a total of 16 combinations of virtual\not-virtual inheritances.
Messing around with the different options yields some very unexpected results, and the more I read about virtual inheritances the more I seem to get confused.
Here are some examples:
virtual
I get:A() B(2) A(3) C(3) D()
That makes some sense - B inherits A virtually, and therefore, unless specifically stated otherwise, B(int x) calls A's default constructor.
virtual
I get:A() A(2) B(2) C(3) D()
Why do A's constructors both precede B's and C's? That behavior makes no sense to me.
virtual
I get:A(3) C(3) A(2) B(2) D()
Why would C's constructor precede B's?
I could go on and on. Some of these combinations lead to very unexpected results (to me, at least).
I know the basics of virtual inheritance and understand them in simple examples, and I've seen many questions regarding it. But some of these behaviors still puzzle me.
Is there any specific set of rules these multiple virtual inheritances go by?
EDIT: I've been redirected to this question: Mixing virtual and non-virtual inheritance of a base class Whlie that helps a lot, I am still confused as to which of A's constructors are called under any specific set of circumstances. I still need some help.
There are two big keys here:
Virtual base classes are initialized directly by the most derived class constructor, and not indirectly by other base classes.
Virtual base classes are initialized before any non-virtual base classes.
So let's look at your examples.
When setting only B:A to virtual I get:
A() B(2) A(3) C(3) D()
That makes some sense - B inherits A virtually, and therefore, unless specifically stated otherwise, B(int x) calls A's default constructor.
B
does not call A
's constructor at all. D
calls A
directly for the virtual instance of A
. But since the constructor of D
doesn't specify how to initialize A
, you get A
's default constructor.
The A
in B(int i) : A(i)
gets ignored not because A
is a virtual base, but because B
is not the most derived class and so doesn't get to construct its A
.
When setting only C:A to virtual I get:
A() A(2) B(2) C(3) D()
Why do A's constructors both precede B's and C's? That behavior makes no sense to me.
Here C::A
is the only virtual base class, so it gets initialized first. Again, since the constructor of D
doesn't specify an initializer for A
, that virtual subobject uses the default constructor.
Then the non-virtual base classes go in order. B
comes before C
, but B
first needs to initialize its non-virtual A
(with 2
).
When setting only D:C to virtual I get:
A(3) C(3) A(2) B(2) D()
Why would C's constructor precede B's?
The only virtual base class is C
, so it gets constructed before B
. But this time the constructor of C
must first initialize its non-virtual base subobject C::A
. Then comes B
, which must first initialize its non-virtual subobject B::A
.
Finally, there's the "normal" pattern, which makes both A
inheritances virtual, resulting in only one A
subobject, which is the entire point of virtual inheritance. It's pointless to make B
or C
virtual unless you expect D
to be reused as a base class in an even more complicated way.
#include <iostream>
using namespace std;
struct A{
A() { cout << "A()" << endl; }
A(int i) { cout << "A(" << i << ")" << endl; }
};
struct B : virtual public A{
B(int i) : A(i){ cout << "B(" << i << ")" << endl; }
};
struct C : virtual public A{
C(int i) : A(i){ cout << "C(" << i << ")" << endl; }
};
struct D : public B, public C{
D() : A(1), B(2), C(3){ cout << "D()" << endl; }
};
void main() {
D d;
system("pause");
}
Output:
A(1) B(2) C(3) D()
Note I've added an initializer A(1)
to D
, to show that a virtual subobject does not necessarily need to use the default constructor. You could have done this in any of your examples where at least one A
inheritance is virtual.