Search code examples
c++derived-classdefault-constructor

Why should a derived class's constructor use the base's default constructor in it's initializer list?


Here is an example of my question:

class MyBaseClass
{
 public:
    MyBaseClass(): my_bool(false), my_value(0)
    {}
    MyBaseClass(bool b, int i): my_bool(b), my_value(i)
    {}

 private:
    bool my_bool;
    int my_value;
}

class MyDerivedClass1 : public ::MyBaseClass
{
 public:
    MyDerivedClass1(double d): my_double(d)
    {}
 private:
    double my_double;
}

class MyDerivedClass2 : public ::MyBaseClass
{
 public:
    MyDerivedClass2(double d): MyBaseClass(), my_double(d)
    {}
 private:
    double my_double;
}

Why isn't the MyDerivedClass1 an ok way to initialize my derived class versus having to explicitly initialize the base class like in MyDerivedClass2?

I guess I don't understand why I can't just rely on C++ calling my base constructor? I know if I wanted them initialized to something different I'd have to call the other constructor in my initialization list, but all I want is the base constructor to be called anyway.


Solution

  • There is no difference between providing a default-constructed base class in the initializer list or not providing it. What you use if entirely your style. (or the company)

    In general, I would say you have 2 options (let's keep constructors out of scope), assuming you always initialize your members.

    Option 1: Initialize all members in the init-list.

    This option should be used if you are C++98-compatible. It has as advantage that you are defining all construction parameters in a single list, making it easy to search through. (Unless you have 50+ members)

    The disadvantage of this option is a lot of duplication when you have multiple constructors.

    With this, you can have 3 variants for them:

    • Skip the default-initialized classes, this makes the list shorter though it's hard to check if you have intended to 'forget' it. (Think replacing a class by a ptr to it)
    • Default initialize all members, this makes the list longer, though indicates clearly the intent
    • Explicitly provide the class you are initializing, by copy contructing with a tempuary

    Option 2: Initialize all members on declaration, except for constructor parameters

    This option assumes you initialize everything in the class declaration. Yet again you can explicitly call the default constructor or not, preferably with braced init, as round braces are interpreted as a function declaration.

    In the initializer list you only have to put the members which are linked to the construction parameters.

    The advantage of this option is readability (especially for large classes). The disadvantage is that this limits your compiler options to 'modern' compilers.

    Base classes

    If we again consider base classes and look at them as if they were members, the consistent way would be to declare them explicitely for option 1 and don't write them for option 2.

    Personnally I like option 2, as I too often encounter classes with too many members and checking if all members are initialized is easier by hand with this one.

    Option 1 however is often used because of legacy in order to keep the code consistent.

    POD

    Important to mention are the PODs (Plain old datatype), like int, double ... If you don't initialize them you get some random data. This makes it important to explicitly intialize them, regardless of the method you use to do so.

    Conclusion

    So in the end, it's all a matter of style with no functional difference.