Search code examples
c++multiple-inheritancec++98virtual-inheritancediamond-problem

Forced to call the base constructor when using virtual inheritance although it will never be called?


I have a class Base which has a parameterized constructor and two classes Middle1 and Middle2 which virtually inherit from Base (in order to solve the diamond problem). In addition, class Foo inherits from Middle1 and Middle2.

Foo now calls the Base constructor explicitly and passes the parameter.

class Base
{
private:
    int value;
protected:
    Base(int& value) { this->value = value; }
};

class Middle1 : virtual public Base
{
protected:
    Middle1() { }
};

class Middle2 : virtual public Base
{
protected:
    Middle2() { }
};

class Foo : public Middle1, public Middle2
{
public:
    Foo(int& value) : Base(value) { }    
};

However, the code does not compile because Base lacks a default constructor and thus Middle1 and Middle2 don't have a default constructor to call, but need one which can be called by Foo.

Of course, I could now change the constructors of Middle1 and Middle2 to call the constructor of Base, too:

Middle1(int& value) : Base(value) { }
//...
Middle2(int& value) : Base(value) { }
// and then change my constructor of Foo:
Foo(int& value) : Base(value), Middle1(value), Middle2(value) { }  

However, it seems rather clunky - especially given that because of the virtual inheritance, the Base constructor will never be called by Middle1 or Middle2 (they are not meant to be initialized, i.e. are basically abstract classes). Thus, having to introduce the parameter to the constructors of both Middle1 and Middle2 seems rather useless.

Is there any other way to make the above code compile without also having to introduce parameterized constructors in Middle1 and Middle2?


Solution

  • In virtual inheritance, the most-derived class has to directly call all of its ancestor constructors. Since Base doesn't have a default constructor, Middle1() and Middle2() can't compile if they can't pass an int& to Base().

    However, in the code you have shown, there is no reason for Base() to take an int by reference. Pass it by value instead, and then Middle1() and Middle2() can pass 0 to Base():

    class Base
    {
    private:
        int value;
    protected:
        Base(int value = 0) { this->value = value; }
    };
    
    class Middle1 : virtual public Base
    {
    protected:
        Middle1() { }
    };
    
    class Middle2 : virtual public Base
    {
    protected:
        Middle2() { }
    };
    
    class Foo : public Middle1, public Middle2
    {
    public:
        Foo(int value) : Base(value) { }    
    };
    

    Though, I would suggest passing a pointer (or a std::optional) instead:

    class Base
    {
    private:
        int value;
    protected:
        Base(int* avalue = nullptr) { if (avalue) this->value = *avalue; }
    };
    
    class Middle1 : virtual public Base
    {
    protected:
        Middle1() { }
    };
    
    class Middle2 : virtual public Base
    {
    protected:
        Middle2() { }
    };
    
    class Foo : public Middle1, public Middle2
    {
    public:
        Foo(int& value) : Base(&value) { }    
    };