Why must base class be constructed before derived members?

I ran into this problem when trying to encapsulate behavior into an existing, rigid structure of a project I'm working on.

See both the DerivedBad and DerivedGood examples below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
using namespace std;

class Base {
public:
    Base(int foo)
    {
        cout << "Base, foo = " << foo << "\n";   
    }
};

class DerivedBad : public Base {
public:
    DerivedBad()
      : fab_(43),
        Base(fab_)
    { 
        cout << "Derived, fab = " << fab_ << '\n';  
    }
    
    int fab_;
};

//------------------------------------------

class BasePrereqs {
protected:
    BasePrereqs(int fab)
      : fab_(fab)
    {
        
    }
    
    int fab_;
};

class DerivedGood : public BasePrereqs, public Base {
public:
    DerivedGood()
      : BasePrereqs(43), Base(fab_)
    {
        cout << "Derived, fab = " << fab_ << '\n';  
    }
};

//------------------------------------------

int main() {
    
    cout << "Bad (UB):" << endl;
    DerivedBad d1;
    
    cout << endl;
    
    cout << "Good:" << endl;
    DerivedGood d2;
}


Output (perhaps):
Bad (UB):
Base, foo = 86
Derived, fab = 43

Good:
Base, foo = 43
Derived, fab = 43


DerivedBad is bad because it suggests that Base is being constructed after fab_, but in reality, the Base class is always constructed before any derived member variables, so 'foo' is uninitialized.

My question is: why? What advantage was there to designing the language like this, vs. giving the user the freedom to construct Base when they wanted?

I guess it's more philosophical, but I'm wondering if there are any concrete edge cases this restriction solves, or similar. I feel like I shouldn't have to make this "prereqs" base class just to force a certain order of construction.

(Probably the biggest issue I have is that DerivedBad even compiles! But that's a general issue with C++ outside the scope of this post.)
Last edited on
The issue is the ordering of execution of the class constructor initialisations for DerivedBad. The initialisation isn't done in the order specified but in some other order. See
https://stackoverflow.com/questions/1242830/what-is-the-order-of-evaluation-in-a-member-initializer-list

So Base() is called before fab_ is initialised. Consider:

1
2
3
4
5
6
7
8
9
10
11
class DerivedBad : public Base {
const static int init { 43 };

public:
	DerivedBad() : fab_(init), Base(init)
	{
		cout << "Derived, fab = " << fab_ << '\n';
	}

	int fab_;
};


which displays:


Bad (UB):
Base, foo = 43
Derived, fab = 43

Good:
Base, foo = 43
Derived, fab = 43


as expected.
Last edited on
Hmm, so use a copy constructor for fab_ and make both fab_ and Base rely on the same, common 'init' data.

Note that your excerpt still misleadingly initializes Base before fab_, but yes I see what you mean. I could try to adapt that. Thanks.
Last edited on
Base will be initialised before fab_ irrespective of the ordering in the constructor - that is as specified by the standard. See the stackoverflow reference.


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

(13.1) — 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.

(13.2) — 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).

(13.3) — 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).

(13.4) — Finally, the compound-statement of the constructor body is executed.


13.2 for base then 13.3 for data members.

Last edited on
Yep, I get that, we're on the same page, but putting Base as the second item in the list can be misleading in that regard (and generates a compiler warning at least on GCC).
Last edited on
MS VS doesn't produce a warning - which would be useful as I've been down this rabbit hole before! The trap is that execution order isn't the same as the specified order.
Registered users can post here. Sign in or register to post.