0

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.

  • 1
    Pleade fix your code formatting. As it is, it's very hard to read. Hint: [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html). – Jesper Juhl Dec 26 '17 at 15:39
  • TL;DR - dreaded diamond shape inheritance? Short answer - do not do it. I am yet to see a real-life example of this, other than textbook or an interview question. – SergeyA Dec 26 '17 at 15:43
  • Thank you for the reformatting. I apologize, the previous format was easier for my eyes to have a direct view of all the classes. Any ideas of redesigning or correcting this case? – Evangelos Katsavrias Dec 26 '17 at 15:47
  • In the classes, I don't think using a base class constructor does what you think it does. – Eljay Dec 26 '17 at 15:49
  • Thank you for the response. In my real case, the E class is constructed in the same way like D, and uses some of the virtual methods of C. – Evangelos Katsavrias Dec 26 '17 at 15:54
  • You should **never** use **any** virtual functions in constructors! (and destructors) – Fureeish Dec 26 '17 at 16:04
  • No I am not using them in the constructors. Just inherit them from C, for after construction use of E. – Evangelos Katsavrias Dec 26 '17 at 16:07
  • Possible duplicate of [Calling a virtual base class's overloaded constructor](https://stackoverflow.com/questions/3495139/calling-a-virtual-base-classs-overloaded-constructor) – 1201ProgramAlarm Dec 26 '17 at 16:39
  • @SergeyA There is nothing "dreaded" or wrong with "diamond" – curiousguy Jan 05 '18 at 01:54

1 Answers1

4

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) {}
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thanks for the references. Please, a clarification here: The constructor of D (the one used by a passed integer) is pretty explicit of how to construct the virtual base B in first place. So, as soon as the compiler will just redefine the same constructor for E, why it fails to use the correct virtual base constructor? – Evangelos Katsavrias Dec 26 '17 at 16:30
  • @EvangelosKatsavrias - The initialization in `D` of `B` doesn't happen. The most derived object type isn't `D`, it's `E`. And it can't be allowed to happen, if you think about it. A virtual base means there is only **one `B` sub-object**. How can `D` initialize it implicitly given that `C` also initializes it? (It's default initialized in `C`). – StoryTeller - Unslander Monica Dec 26 '17 at 16:34
  • Good. I had a misunderstanding of the compiler's actions with the "using" keyword in class E, in order to inherit the constructors of D. As it is clear for me now, it just calls the base (class D) construction, at which stage the class D is not the most derived class of course, etc... Thank you for your help. – Evangelos Katsavrias Dec 26 '17 at 17:04
  • Right, and `using` only passes the initializer list to the constructor named there, all other bases become default initialized. So `B` is default initialized first via `C` (not *by* `C`) due to the left-to-right rule by `E` itself, and even though `C` itself would have passed the parameter itself, that call is skipped because `E` had already called a constructor of `B`. – Ext3h Dec 26 '17 at 17:36