Search code examples
c++oopinheritanceinitializationmultiple-inheritance

How do I keep from calling default constructor in base class intializers?


I am trying to construct Derived3 in such a way that the non-default constructors are called when d2 is initialized. I would have expected that when d2 was initialized, none of the default constructors would be called. With this code:

#include <string>
#include <iostream>

struct Base
{
    Base() : _message("Value initialized by default constructor")
    {
        std::cout << "Base default constructor called" << std::endl;
    }
    Base(std::string message) : _message(message)
    {
    }

    std::string     _message;
};

struct Derived1 : virtual public Base
{
    Derived1() : Base()
    {
        std::cout << "Derived1 default constructor called" << std::endl;
    }
    Derived1(std::string message) : Base(message)
    {   
    }

};

struct Derived2 : virtual public Base
{
    Derived2() : Base()
    {
        std::cout << "Derived2 default constructor called" << std::endl;
    }
    Derived2(std::string message) : Base(message)
    {
    }
};

struct Derived3 : virtual public Derived1, virtual public Derived2
{
    Derived3() : Derived1(), Derived2()
    {
        std::cout << "Derived3 default constructor called" << std::endl;
    }
    Derived3(std::string message) : Derived1(message), Derived2(message)
    {
    }
};

int main()
{
    Derived3 d1 = Derived3();
    std::cout << d1._message << std::endl; // You get what you expect.

    Derived3 d2 = Derived3("Not initialized by default constructor");
    std::cout << d2._message << std::endl; // You get what you do not expect.
}

I would have expected that d2._message would be "Not initialized by default constructor", when in actuality it is "Value initialized by default constructor". Full output is:

Base default constructor called
Derived1 default constructor called
Derived2 default constructor called
Derived3 default constructor called
Value initialized by default constructor
Base default constructor called
Value initialized by default constructor

Expected Output:

Base default constructor called
Derived1 default constructor called
Derived2 default constructor called
Derived3 default constructor called
Value initialized by default constructor
Not initialized by default constructor

Why is this happening and how do I attain the expected behavior?


Solution

  • When you virtually inherit a base class, in all cases the virtually-inherited base class can be thought of as being always the immediate superclass of the so-called "most-derived" class. Change your Derived3 constructor as follows:

    Derived3(std::string message) : Derived1(message), Derived2(message),
                    Base(message)
    

    Base is really also the base class of your Derived3, because it gets virtually inherited from Derived1 (and Derived2). That's what virtual inheritance is.

    If you don't want Base to be default-constructed, you have to invoke the appropriate constructor yourself, here.

    Even though you don't explicitly declare Derived3 as inheriting from Base, it inherits it virtually, hence you can invoke its constructor from Derived3.

    Note that if you declare Derived4 as a subclass of Derived3, this constructor of Base here won't get called. Derived4 will virtually inherit Base, and it will be responsible for constructing it.

    What's really happening when you have virtually-inherited classes, is that every constructor you declare can be thought of as actually resulting in two actual constructors, in reality: a constructor that's responsible for constructing all virtually-inherited classes, and a constructor that does not. This Derived3 constructor you declared above: you actually end up with two constructors from this. Two for the price of one: one that will construct Base, and one that will not. The one that will construct Base gets used when Derived3 is constructed directly, and is the most-derived class. The second constructor is the same, except that it will not construct Base, and it gets used if a subclass of Derived3 gets instantiated. You see it as one constructor, but the compiler does a lot more work, creates two of them, and makes sure that the right one gets used, when something needs to be constructed.