user2630165
user2630165

Reputation: 311

virtual inheritance in c++ misunderstood

I read that in virtual inheritance, constructors are called "from the most derived". consider the following code. In my opinion, the most derived class here is D. then B and C and the "most-not-derived" is A. So how come the most "Base" constructor is called first and not the "most derived"? Thanks.

#include <iostream>
using namespace std;

struct A
{
    A()
    {
        cout << "A default constructor" << endl;
    }
};

struct B : virtual public A
{
    B() : A()
    {
        cout << "B default constructor" << endl;
    }
};

struct C : virtual public A
{
    C() : A()
    {
        cout << "C default constructor" << endl;
    }
};

struct D : public B, public C
{
    D() : B(), C()
    {
        cout << "D default constructor" << endl;
    }
};

int main()
{
    D d;
}

This is the output :

A default constructor
B default constructor
C default constructor
D default constructor

UPDATE:


Ok. so consider the following code. Notice that 10 was printed although D,B and C constructors sent 7. Here actually the base class IS the first one that is called. There was no chain from D to B to A. A() was called first (actually it's default constructor). and only then B and C constructors were called.

But I read : "from the most derived." Source : c++ virtual inheritance

the most derived here is D then B and C and only then A. So how come that A is called first without even considering the parameters B,D,C transfer to it from their constructors ? Thanks.

The code :

#include <iostream>
using namespace std;

struct A
{
    int _x;
    A()
    {
        _x = 10;
        cout << "A default constructor" << endl;
    }
    A(int x)
    {
        _x = x;
        cout << "A NOT-default constructor" << endl;
    }
};

struct B : virtual public A
{
    B(int x=7) : A(x)
    {
        cout << "B  constructor" << endl;
    }
};

struct C : virtual public A
{
    C(int x=7) : A(x)
    {
        cout << "C  constructor" << endl;
    }
};

struct D : public B, public C
{
    D(int x=7) : B(x), C(x)
    {
        cout << "D  constructor" << endl;
    }
};

int main()
{
    D d;
    cout << d._x;
}

The output :

A default constructor
B  constructor
C  constructor
D  constructor
10

Upvotes: 1

Views: 157

Answers (5)

Deduplicator
Deduplicator

Reputation: 45704

Construction order is quite simple in C++:

  1. You call a most-derived ctor (By initializing a variable; auto-storage-class, static, dynamic, whatever).
  2. That ctor initializes all sub-objects:
    • A delegating ctor calls another most-derived-ctor.
    • A non-delegating ctor does the work itself:
      1. Iff the most-derived ctor, virtual bases in left-first declaration-order. (That means arguments to the virtual-base ctor given by non-most-derived-ctors are ignored)
      2. The other direct bases in left-first declaration-order.
      3. The members in declaration-order.
  3. The ctors body runs.

As your most-base ctor is for the only virtual base, its body is the first ctor-body to run, called directly by the most-derived ctor.

In general, calling virtual functions, typeid and dynamic_cast is safe, though not before the base-subobjects are all initialized: May I call a virtual function to initialize a base-class sub-object?

Upvotes: 6

Mike Seymour
Mike Seymour

Reputation: 254771

That means that it's the responsibility of the most derived class to initialise any virtual base sub-objects, as well as those that it immediately derives from. That is, all the base constructors are called from the most-derived constructor: the constructor for D calls the constructor for A, then B and C, and finally initialises itself. This is necessary to ensure that the shared base object is initialised just once, and before any of the classes that derive from it.

It doesn't mean that the order is from the most to least derived. As with regular inheritance, base sub-objects are always initialised first, so they're available when initialising the derived classes.

To answer your updated question, since D initialises A, it will call the default constructor unless its initialiser list contains an entry for A:

D(int x=7) : B(x), C(x)        // calls A(), initialising with 10
D(int x=7) : A(x), B(x), C(x)  // calls A(int), initialising with 7

Any entry for A in the initialiser list of B (or C) is only used when B (or C) is the most derived class (and so is responsibly for initialising A).

Upvotes: 4

galdin
galdin

Reputation: 14074

That is how objects are built.

UPDATE

Check this example out:

class A{
    A()
    {
    cout << "Constructor A"<<endl;
    }

    ~A()
    {
    cout << "Destructor A"<<endl;
    }
}

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

    ~B()
    {
    cout << "Destructor B"<<endl;
    }
}
class C : public B{
    C()
    {
    cout << "Constructor C"<<endl;
    }

    ~C()
    {
    cout << "Destructor C"<<endl;
    }
}

Creating an object of class C:

C obj;

The output will be as follows:

Constructor A
Constructor B
Constructor C
Destructor C
Destructor B
Destructor A

The reason for the execution is this:

When a class derives from another class, it derives the properties of that class. Functionalities of the derived class may or may not depend on the functionalities of the base class, but it can never be the other way. Assuming the derived class depends on the base class functionalities, it is important that the base class is properly initialized before the derived class can be initialized.

UPDATE:

When an object of C is made, he control from C constructor is transferred to it's base class' constructor, before C's constructor can execute. That's what I meant by base class first.

UPDATE:

Your question can be best answered by drawing the object relationship.
We'll have A right on the top and D at the bottom.

"in virtual inheritance, [virtual base] constructors are called from the most derived" [type's constructor]. "

By the above statement, they're asking you to start from the most derived type's constructor (D) and transverse up to the most base class' (A) constructor.

UPDATE:

@leemes has made the execution flow clearer in the comments:

It is the constructor itself which "redirects" to the base's constructor. After this returns, it continues with its own constructor. What you miss is that what is written in curly braces is not the whole implementation. It's only what comes after the call to the base ctor and initializing the member variables. Same with ctor: You write in curly braces what is to be executed before calling the ctor of member variables and then the ctor of the base.

Upvotes: 0

R Sahu
R Sahu

Reputation: 206747

You said

I read that in virtual inheritance, constructors are called "from the most derived".

That is true. Let me elaborate.

In your case, D is the most derived.

When you construct an instance of D, the constructors for A is called from the constructor of D since there is only instance of A for each instance of D. Constructor for A will not be called from the constructor of B or C.

When you construct an instance of B, then constructor for A is called from the constructor of B. Similarly for an instance of C.

If you had a sub-type of D,

struct E : public D
{
};

and you create an instance of E, then the constructor of A will be called from the constructor of E.

The C++ Draft Standard (N3337) says this about initialization involving virtual base classes:

12.6.2 Initializing bases and members

5 Initialization shall proceed in the following order:

— First, and only for the constructor of the most derived class as described below, virtual base classes shall be 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 class names in the derived class base-specifier-list.

Upvotes: 1

How do you propose any derived class to behave if its constructor code ran before the base class was constructed? While technically possible, it'd be entirely useless.

The behavior you observe is the only sane one, and has nothing to do with virtual inheritance.

Upvotes: 1

Related Questions