3

I know that virtual inheritance enters into the realm of "perhaps you should be doing something different," but I sometimes it is unavoidable. I am a little confused about the preferred way to handle constructor arguments when using virtual inheritance to solve some sort of diamond problem.

Here is a very simple example to help me demonstrate:

#include <iostream>

class Ball {
public:
    auto diameter() const { return _diameter; }
    
protected:
    Ball(unsigned int diameter): _diameter(diameter) {}
    
private:
    unsigned int _diameter;
};

class ColoredBall : virtual public Ball {
public:
    enum class Color {
        RED,
        GREEN,
        BLUE
    };
    
    auto color() const { return _color; }
    
protected:
    ColoredBall(unsigned int diameter, Color color):
        Ball(diameter),
        _color(color)
    {
        
    }
    
private:
    Color _color;
};

std::ostream& operator<<(std::ostream& os, const ColoredBall::Color& v) {
    switch(v) {
    case ColoredBall::Color::RED:
        os << "RED";
        break;
    case ColoredBall::Color::GREEN:
        os << "GREEN";
        break;
    case ColoredBall::Color::BLUE:
        os << "BLUE";
        break;
    }
    
    return os;
}

class BouncyBall : virtual public Ball {
public:
    auto bounciness() const { return _bounciness; }
    
protected:
    BouncyBall(unsigned int diameter, double bounciness):
        Ball(diameter),
        _bounciness(bounciness)
    {
        
    }
    
private:
    double _bounciness;
};

class MyBall : public ColoredBall, public BouncyBall {
public:
    MyBall():
        Ball(10),
        ColoredBall(20, Color::GREEN),
        BouncyBall(30, 5.5)
    {
        
    }
};

int main() {
    MyBall b;
    
    std::cout << "b.diameter() = " << b.diameter() << std::endl;
    std::cout << "b.color() = " << b.color() << std::endl;
    std::cout << "b.bounciness() = " << b.bounciness() << std::endl;
        
    return 0;
}

My base class Ball's constructor requires a single argument used to initialize a member variable diameter. Thus, every Ball subclass, has a diameter variable (yes, I know this could be accomplished using virtual method as well).

Next I have two subclasses of Ball (ColoredBall and BouncyBall) which both add their own member variables (they may also add some more features). Here is where my confusion starts. Since, for example, ColoredBall "is a" Ball, in order for its constructor to be complete, it must call the (non-default) constructor of Ball and thus must provide a diameter. So, it seems logical make this an argument of ColoredBall's constructor as well and "forward" this along to Ball's constructor. The same applies for BouncyBall.

Here's the "weirdness." If I now create a subclass MyBall which is both a ColoredBall and a BouncyBall, I need to explicitly call the constructor of Ball because Ball has been inherited virtually in both ColoredBall and BouncyBall. It is this explicit call that really initializes the Ball class. This means that, even though I have to pass the diameter parameter to both the ColoredBall and BouncyBall constructors, it is not used at all. This means (as I show in my example), I could pass completely different values to these constructors and only the one passed explicitly to Ball will really matter.

While I understand why this happens, I am wondering if there is a better way to handle/express this?

EDIT As an aside, for those familiar with Qt, I have this problem crop up quite a few times when dealing with QObjects. Sometimes, I want to create a few interfaces (e.g., classes A and B) that rely on the Qt meta-object system, thus I inherit from QObject. However, what if I want to create a class that inherits from both of these interfaces (e.e., class C), this is impossible.

2
  • Use interfaces to decorate your inherited classes. Commented Apr 8, 2022 at 14:30
  • Perhaps you could use one pair of constructors ? A public one that initializes virtual bases, and a protected one that default-initializes them. Commented Apr 8, 2022 at 15:17

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.