2

I have couple question regarding how the following code runs

using namespace std;
class A
{
    int i;
public:
    A() { i = 7; cout << 1 << f() << i; }
    A(int i) :i(i) { cout << 1 << f() << i; }
    char f() { return 'A'; }
};
class B : public virtual A
{
    int i;
public:
    B(int i) : A(i), i(++i) { cout << 2 << i; }
    virtual char f() { return 'B'; }
};
class C : public virtual A {
public:
    C(int i) : A(i) { cout << 3 << i; }
    virtual char f() { return 'C'; }
};
class D :public A {
public:
    D(int i) { cout << 4 << i; }
    virtual char f() { return 'D'; }
};
class E : public B, public C, public D {
public:
    E() : B(2), C(3), D(4) { cout << 5; }
    virtual char f() { return 'E'; }
};
int main()
{
    E e;
    return 0;
}

So the output should be '1A723331A7445'.

  • First of all, I want to double check that I am correct in thinking the following: it begins with printing '1A7' instead of '1A2' becuase the i being inputted into A is uninitialized. Do I understand this correctly?
  • Second, I was wondering why C(3) prints '33' before it prints '1A7'

1 Answers1

1

Right, so let me start off by saying you've created a truly messed up type hierarchy. If you were trying to understand initialization order, this example is only likely to confuse you.

Anyway, to make things clearer, I modified your code and added a slash / character to the end of each c'tor print statement. So we can more easily discern what part of the line belongs to each c'tor. This gives the following output:

1A7/23/33/1A7/44/5

Before I get to the initialization order, you should know that all the virtual functions you specified won't by dynamically dispatched. A virtual function in the c'tor body will be statically bound. So for our intents and purposes, you don't really have virtual functions called in your code.

Now, to quote the C++ standard, this is how the initialization order will be determined ([class.base.init]/13):

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

  • First, and only for the constructor of the most derived class, 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.

  • Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

  • Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

  • Finally, the compound-statement of the constructor body is executed.

So let's break your initialization apart:

1) The virtual A sub-object is default constructed, since you didn't specify it in the member initializer list of E(), it executes A() for the object that is shared for B and C, and prints 1A7/.

2) Now the c'tor for B is called, executing B(int i) with i = 2. It sets B::i to 3, and the c'tor body prints 23/.

3) C is constructed by calling C(int i) with i = 3. This prints 33/.

4) Now it's time to construct D. So you call D(int i) with i = 4. Since D inherits from A non-virtually, it will have a distinct A sub-object that needs construction now.

  1. You again didn't specify a parameter for it in the member initializer list, so A is default constructed. This prints 1A7/.

  2. Now the body of D(int i) runs, and prints 44/.

5) Finally, the body of E() is called, and prints 5.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • So the A(i) being initialized before the b or c constructors does nothing? I don't understand why the `C(int i) : A(i) { cout << 3 << i; }` doesnt construct a A(i). – CluelessButCurious Sep 17 '17 at 19:42
  • 1
    @CluelessButCurious - Because it's a virtual base. Virtual bases are constructed only once by the most derived class (this being `E`). – StoryTeller - Unslander Monica Sep 17 '17 at 19:44
  • So for instance: if we changed D to `D(int i) : A(i){ cout << 4 << i; }` this would behave differently and actually call the constructor a second time right? – CluelessButCurious Sep 17 '17 at 19:47
  • 1
    @CluelessButCurious - It already *does* call the c'tor a second time (`D` doesn't inherit virtually, so it needs a distinct `A` sub-object). It will just invoke a different c'otr (the one accepting an `int`). – StoryTeller - Unslander Monica Sep 17 '17 at 19:49
  • Oh! So basically, since the virtual base is called at the beginning for E(), it is reused (since it is virtual) until we get to D, at which point it needs to construct another A() since D isn't `class D :public virtual A`. The lists in B and C is kind of a red-herring. Did I understand you correctly? – CluelessButCurious Sep 17 '17 at 19:58
  • 1
    @CluelessButCurious - Not quite... there are no red-herrings. If you construct an object where `B` or `C` is the most derived class (`B b(1);` or `C c(2);`) that initializer for `A` will be used. – StoryTeller - Unslander Monica Sep 17 '17 at 20:02
  • Ok. I understand that `A()` is constructed again for D becuase D isn't `class D :public virtual A`. Where I'm stuck is, why the `C(int i) : A(i) {}` instead of just `C(int i) {}` isn't a red-herring when the two behave the same. – CluelessButCurious Sep 17 '17 at 20:11
  • @CluelessButCurious - [This is a subject better learned in a structured manner](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). Like I said, it's not the same. It depends on what is most derived type that is being constructed. – StoryTeller - Unslander Monica Sep 17 '17 at 20:15