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.)