c++multiple-inheritanceambiguousvirtual-inheritance

How to handle multiple inheritance when both inherited classes need a distinct member?


I have the following classes:

class ServoPart {
protected:
    virtual void doJob(byte* job) = 0;

private:
    bool moving;
    Servo servo;
};

// the following classes only have a constructor so I can use two ServoParts to inherit from
class Major: public ServoPart {};
class Minor: public ServoPart {};

class Arm: public Major, public Minor {
private:
    void move() {
        // do stuff
        if (target == current) {
            moving = false;
        }
    };

public:
    void doJob(byte* job) {/* do stuff */};
};

I can't use virtual inheritance (I think) because the Major and Minor need to control one servo each which can't be the same. However, when the Arm is done moving, it should set the moving member to false. Intellisense shows ServoPart::moving, when im typing moving.

Would this access of moving be ambiguous? If yes, how can I fix this? Is my assumption about virtual inheritance, that I can't use it because I have two different servos, correct?


Solution

  • Yes, it is ambiguous and the compiler will complain. You can write Major::moving or Minor::moving within the code of Arm's member functions to specify which you want.

    I question whether you have a proper "isa" relationship here. An arm is not a motor. An arm has two motors. You should be using composition here instead of inheritance. Note that Major and Minor don't have any virtual functions so there is no reason to prefer inheritance.

    Try:

    class Arm {
       Major major;
       Minor minor;
       void move() {
         ...
         major.moving= false;
         minor.moving= false;
         ...
    

    It is more obvious now how to refer to the components. But with (multiple) inheritance it is the same idea. I think also you are introducing the names Major and Minor just to get around the limit of inheriting more than one copy. If you used composition, you can avoid that:

    class Arm {
       ServoPart major;
       ServoPart minor;
    ...
    

    Or, since they are the same type now, perhaps make an array instead. This makes it easy to "broadcast" the same operation to all of them:

    class Arm {
        ServoPart joints[2];
        void move() {
            ...
            for (auto j : joints)  j.moving= false;
    

    update

    Your comment indicates ServoPart has a virtual doJob function. You should separate that into a pure interface that has no data members, and derive your single-motor class from that. Your composited class can also inherit from that interface.

    struct Servo_Interface {
        virtual void doJob() =0;
    };
    
    class Joint : Servo_Interface {
        bool moving;
        Servo servo;
    public:
        void doJob()  override;
    };
    
    class Arm : Servo_Interface {
        Joint joints[2];
    public:
        void doJob()  override;
    };
    

    Note that the implementation of doJob in Arm can call doJob for each of the components it contains, but it is a separate function that does not have any automatic reliance on them. Both single Joint and Arm instances can be treated polymorphically since they have a common interface.