Evangelos Katsavrias
Evangelos Katsavrias

Reputation: 19

Using base constructors with virtual multiple inheritance

Running the code below I am expecting to get the following console output:

B int v

D

Instead of that, the constructor of E is calling the default constructor of B and the result is:

B

D

One way of achieving the correct construction, is to re-declare the same constructors of D in E (i.e. the commented code part), but I still hope for a better solution than that.

Ready to run code, with -std=c++11 flag:

#include <iostream>

class A {
public:
  virtual void fun() = 0;
  virtual void fun2();
  void fun3();
};

class B : public A {
public:
  B();
  B(int);
  void fun();
  void fun2();
};

class C : virtual public B {
public:
  using B::B;
  void fun();
};

class D : virtual public B {
public:
  D();
  D(int);
  void fun();
};

class E : public C, public D {
public:
  using D::D;
  void fun();
};

void A::fun2() {}
void A::fun3() {}
B::B() { std::cout << "B\n"; }
B::B(int v1) { std::cout << "B int v\n"; }
void B::fun() {}
void B::fun2() {}
void C::fun() {}

D::D() { std::cout << "D\n"; }
D::D(int v1) : B(v1) { std::cout << "D\n"; }
void D::fun() {}

/*E::E(int v1): D::B(v1){  std::cout <<"E\n";}  */ void E::fun() {}

int main() {
  E Eob(1);
  return 0;
}

Conclusion: Eventually, defining explicit constructor for E, with an explicit call to the virtual base class B (see commented piece of code), is necessary.

As Eljay correctly commented in the very first place, I assumed a wrong use of the "using D::D". The "using" keyword, never redefines constructors for E, which would be similar to that of D; it just calls the constructors of the base class D, and forces the base class D construction. The latter fact, triggers the hierarchy of virtual base classes construction (as StoryTeller replied below) and causes my problems to construct as desired an object of class E.

Upvotes: 0

Views: 754

Answers (1)

This is a fairly common pitfall. First let me say that the presence of A is a red herring. You could have made your example shorter by omitting it entirely.

The reason you don't see B(int) used is due to two clauses in the C++ standard. First [class.inhctor]/8 says:

An implicitly-defined inheriting constructor performs the set of initializations of the class that would be performed by a user-written inline constructor for that class with a mem-initializer-list whose only mem-initializer has a mem-initializer-id that names the base class denoted in the nested-name-specifier of the using-declaration and an expression-list as specified below, and where the compound-statement in its function body is empty ([class.base.init]).

Which says that the c'tor inherited from D in E is translated to something like this:

E::E(int i) : D(i) {}

And that is unfortunately your problem. Because upon consulting [class.base.init/10]:

In a non-delegating constructor, initialization proceeds in the following order:

  • First, and only for the constructor of the most derived class ([intro.object]), 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.

We see (emphasis mine) that it is only the most derived c'tor that can and will initialize the virtual base. And how is the most derived c'tor doing that? As we wrote previously. It omits the virtual base from its member initializer list. So the virtual base is default initialized.

If you want to pass an integer to B's c'tor. You need to define E's constructor yourself:

E::E(int i) : B(i), D(i) {}

Upvotes: 4

Related Questions