C++ Virtual Inheritance

C++ Virtual inheritance and constructors

There are two big keys here:

  1. Virtual base classes are initialized directly by the most derived class constructor, and not indirectly by other base classes.

  2. 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.

Virtual inheritance impact on constructor

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.

How to avoid virtual inheritance in C++17?

I believe this addresses your question but hard to tell. It is stripped of some of the details in your code but demonstrates the technique. It uses std::variant and std::visit.

#include <iostream>
#include <memory>
#include <variant>

class Car {
public:
Car( ) = default;
};

class ICECar : public Car {
public:
ICECar( ) {
}
void visitCarService( ) {
std::cout << "ICE visitCarService\n";
}
};

class ECar : public Car {
public:
ECar( ) {
}
void visitCarService( ) {
std::cout << "E visitCarService\n";
}
};

using car_variant = std::variant<
std::shared_ptr<ICECar>,
std::shared_ptr<ECar>>;

template <size_t C>
void visitCarService(std::array<car_variant, C>& cars) {
for (auto& c : cars) {
std::visit(
[](auto&& arg) {
arg->visitCarService( );
},
c);
}
}

int main(int argc, char** argv) {

ICECar ice_car { };
ECar e_car { };

std::array<car_variant, 2> cars {
std::make_shared<ICECar>(ice_car),
std::make_shared<ECar>(e_car)
};

visitCarService(cars);

return 0;
}

This compiled using GCC 11 using std=c++17 with -pedantic set. Presumably it should compile under MS. Here is a an online run of it.

C++ Virtual Inheritance of virtual method

Making A a virtual base class of B and C ensures that D contains exactly one A subobject[1]. For this, both B and C provide final overriders for foo[2] and both are inherited by D[2], so D has two final overriders for foo, making the program ill-formed[2].

When A is not a virtual base class of B and C, D will contain two distinct A subobjects[1]. Each of these subobjects will have their own inherited final overrider for foo[2].


[1]: N4140 §10.1 [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.

[2]: §10.3 [class.virtual]/2 (emphasis mine):

If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list, cv-qualification, and ref-qualifier (or absence of same) as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf. For convenience we say that any virtual function overrides itself. A virtual member function C::vf of a class object S is a final overrider unless the most derived class of which S is a base class subobject (if any) declares or inherits another member function that overrides vf. In a derived class, if a virtual member function of a base class subobject has more than one final overrider the program is ill-formed.

How does virtual inheritance actually work?

With a single level of inheritance, there's no difference in behaviour.

The difference is when inheriting from multiple base classes, which themselves have a common base class:

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B,C {};

In this example, with virtual inheritance, D contains just one A sub-object, which its B and C subobjects share; this is the "diamond" pattern:

    A
/ \
B C
\ /
D

Without virtual inheritance, it would contain two A sub-objects, and no "diamond":

   A A
| |
B C
\ /
D

With or without multiple inheritance, there's still a difference if you have more than one level of inheritance: a virtual base class must be initialised by the most derived class, not by its immediate derived class. This is to avoid the ambiguity in the case of multiple inheritance, where (in the example above) both B and C would otherwise be responsible for initialising the shared A.

Is this example working with virtual inheritance in C++?

You need virtual inheritance in this way:

struct A {
virtual int f() = 0;
};
struct B : virtual A {
int f() { return 1; }
};
struct C : virtual A {};
struct D : B, C {};

int main() {
D d;
return d.f();
}

In the dupe I commented you can see this relation

  A  
/ \
B C
\ /
D

for virtual inheritance and

A   A  
| |
B C
\ /
D

without virtual inheritance.

In the second case D contains two function definitions. One is implemented and one isn't.

c++ virtual inheritance doesn't work, how do I use the multiple parents' members?

The result is correct, while the real reason is when you are using virtual inheritance, D ctor first uses AA's default ctor, n is not assigned.
If you want to use AA(name, a), you need to explicitly use that in D.



Related Topics



Leave a reply



Submit