Concerning Struct Constructor and Destructor behavior - C++

Undefined behavior

You're invoking undefined behavior by calling foo before the object is fully initialized. Quote from 12.6.2 in the C++ standard :

Member functions (including virtual member functions, 10.3) can be called for an object under construction. Similarly, an object under construction can be the operand of the typeid operator (5.2.8) or of a dynamic_cast (5.2.7). However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined. [ Example:

class A {
public:
  A(int);
};

class B : public A {
  int j;
public:
  int f();
  B() : A(f()),       // undefined: calls member function
                      // but base A not yet initialized
          j(f()) { }  // well-defined: bases are all initialized
};

class C {
public:
  C(int);
};

class D : public B, C {
  int i;
public:
  D() : C(f()),       // undefined: calls member function
                      // but base C not yet initialized
          i(f()) { }  // well-defined: bases are all initialized
};

— end example ]

In other words, this would be ok according to the standard :

C(int i=0) : B(), A(i) {
    B::b = foo();
}

And this will print 10 instead of the 0 that you got (which could have been anything else, since that was undefined behavior).

Initialization order

Setting aside this matter of undefined behavior, and to address your question, the order in which initialization happens is well-defined :

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

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

[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. — end note ]

So, in your code, the initialization order is : B (B::b), A (A::a), C ().

As noted in the comments below though, changing this initialization order (by eg. using struct C : A, B instead of struct C : B, A) would not however get rid of the undefined behavior. Calling A::foo before the B part is initialized remains undefined, even if the A part is initialized.


This is just another case of undefined behavior. For example, my system gives the following results.

B
A
-858993460
Bye A
Bye B

Try this live demo which produces yet another distinct result (C(10).bar() produced 32764).

foo() can be called in this context, but it will be called before A's constructor. This means a is initialized, which leads to reading an uninitialized variable, which leads to undefined behavior. This is similar to accessing a member before it's initialized. Consider the following example. a is initialized to b's value, then b is initialized. The problem is obvious, b is uninitialized at the point where it's read to initialize a.

struct foo
{
    foo(int x) : a(b), b(x) {}
    int a;
    int b;
};

int main()
{
    foo bar(10);
}