Search code examples
c++abstract-classmultiple-inheritanceoverriding

C++ Multiple Inheritance: Using base class A's implementation for abstract method of base class B


I have an abstract class Interface that declares f() and g() methods.

MyClass implements Interface::g(), and MyClass inherits from Base, which implements its own f().

How can I make Base::f() be the implementation for Interface::f() in MyClass?

I don't want to make Base inherit from Interface directly, if possible.

class Interface {
    public:
        virtual void f() = 0;
        virtual void g() = 0;
};

class Base {
    public:
        void f() { }
};

class MyClass : public Interface, public Base {
    public:
        void g() { }
};

int main() {
    MyClass c;
    c.f();
    c.g();
}

This doesn't work, because f is still a pure virtual function in MyClass.

In Java, I would use the following approach, but it obviously doesn't work in C++.

public class MyClass extends Base implements Interface {
    public static void main(String argv[]) {
        MyClass c = new MyClass();
        c.f();
        c.g();
    }
    public void g() { }
}

class Base {
    public void f() { }
}

interface Interface {
    public void f();
    public void g();
}

The concrete problem I'm trying to tackle is the following:

The Interface in the example above is an abstract class Sender with different methods related to sending MIDI messages, e.g. update() (check if a message should be sent, and send it if necessary), and setAddress() to change the address of the message. The messages are MIDI events. I have to change the address/channel for functions like transposing and selecting different banks.

The Base class is a class Button. It has a method update() that checks whether the button is pressed or not, and calls the (virtual) methods press() and release() accordingly.

MyClass would then implement the press() and release() methods, to send the right data when the button state changes.

There are other types of Senders as well, and I keep them all in a list to update() them all at once, regardless of the specific type of Sender. I also need to be able to change the address of all Senders in the list (all at once).

The setAddress() method has nothing to do with a button, so it doesn't make sense to have Button inherit from Sender.
There are also other classes that would implement Button, but they are not all Senders.

Maybe I have to change the structure entirely, but I don't really see a better solution at the moment.

class Button {
    public:
        void update() {
            if ( /* check in hardware whether button is pressed */ )
                press();
        }
        virtual void press() = 0;
};

class Sender {
    public:
        virtual void update() = 0;
        virtual void setAddress(int address) = 0;
};

class ButtonSender : public Button, public Sender {
    public:
        void update() {
            Button::update();
        }
        void press() {
            /* send a message */
        }
        void setAddress(int address) {
            /* change the address */
        }
};

int main() {
    ButtonSender bs;
    Sender *s = &bs;
    s->update();
}

Solution

  • Thank you, OP, for posting a more concrete example. It has driven me to take the unusual step of posting a second answer. I hope you find it useful.

    What I'm going to do here is to show you an alternative class hierarchy which I hope you will see is better suited to the problem you want to solve. I'm just going to focus on the relationship between class Button (which is where the fundamental problem lies) and something which I'm going to call a ButtonHandler.

    Now a ButtonHandler could be anything - Button certainly doesn't care what it is - it just needs to be notified when the button that it owns is pressed or released. Once you appreciate that that is at the heart of the problem, the solution becomes more obvious. You can, of course, apply this approach to your Sender class (just derive it from ButtonHandler and implement OnPress and OnRelease), but I'll leave that bit to you.

    Finally, as a bit of icing on the cake, I had my Buttons add and remove themselves to / from that list you were talking about so that they can easily all be polled in some kind of loop.

    OK, let's go. Let's start with our ButtonHandler (since this needs to be declared before class Button), which is simple enough:

    #include <set>
    #include <iostream>
    
    class Button;
    
    // Our abstract button handler - derive concrete handlers from this
    class ButtonHandler
    {
    public:
        // Constructor
        ButtonHandler ();
    
        // Destructor (must be virtual!)
        virtual ~ButtonHandler ();
    
        virtual void OnPress () = 0;
        virtual void OnRelease () = 0;
    
    private:
        Button *m_button;
    };
    

    OK, not much to see there. Note the pure virtual methods OnPress and OnRelease - which are there for concrete classes deriving from this one to override - and the forward declaration of class Button. We will need to implement the constructor and destructor bodies later, after they have seen the full declaration of class Button, since they need to call methods on it.

    Now for class Button itself, and the associated ButtonList. Note that, again, because it deals only in pointers to Buttons, ButtonList doesn't need to see the full declaration of class Button, which is kind of handy here:

    std::set<Button *> ButtonList;
    
    // class Button
    class Button
    {
    public:
        // Constructor
        Button (ButtonHandler *button_handler)
        {
            m_button_handler = button_handler;
            ButtonList.insert (this);
        }
    
        // Destructor
        ~Button () { ButtonList.erase (this); }
    
        // Poll the button state
        void Poll ()
        {
            // This would normally check (in hardware) whether button is pressed, but here we just fake it
            m_button_handler->OnPress ();
            m_button_handler->OnRelease ();
        }
    
    private:
        ButtonHandler *m_button_handler;
    };
    
    // Poll all the buttons that exist
    void PollAllButtons ()
    {
        for (auto button : ButtonList) { button->Poll (); }
    }
    

    So what's going on here? Well:

    • The Button constructor is passed a pointer to a ButtonHander so that it can call its OnPress and OnRelease methods when appropriate.
    • The magic of the STL (when available!) makes implementation of ButtonList trivial. It just uses a std::set, which is documented here (see examples on that page and linked pages). Buttons add and remove themselves from this list as they come and go.
    • PollAllButtons iterates through all the Button *s stored in ButtonList and calls what I chose to call Poll for each one, which in turn calls the associated ButtonHandlers OnPress and OnRelease methods as appropriate. This uses something called a "ranged for loop", which works with, AFAIK, all the STL container classes.

    Now the implementation of the ButtonHandler constructor and destructor. These just create and delete the associated button (so, the Buttonhandler owns the button):

    // ButtonHandler constructor - implementation
    ButtonHandler::ButtonHandler ()
    {
        m_button = new Button (this);
    }
    
    // Buttonhandler destructor - implementation
    ButtonHandler::~ButtonHandler () { delete m_button; }
    

    And finally, a concrete button handler and minimal test program that is included here to show you that all this stuff does actually work. You will notice (and this is rather nice) that this is unchanged from the previous code I posted (which was just that bit too clever):

    // An example concrete button handler
    class MyButtonHandler : public ButtonHandler
    {
    public:
        // Constructor
        MyButtonHandler (int handler_id) { m_handler_id = handler_id; }
    
        void OnPress () override { std::cout << "MyButtonHandler::OnPress (" << m_handler_id << ")" << std::endl; }
        void OnRelease () override { std::cout << "MyButtonHandler::OnRelease (" << m_handler_id << ")" << std::endl; }
        // ...
    
    private:
        int m_handler_id;
    };
    
    // Test program
    int main ()
    {
        MyButtonHandler bh1 (1);
        MyButtonHandler bh2 (2);
        PollAllButtons ();
    }
    

    Output:

    MyButtonHandler::OnPress (1)
    MyButtonHandler::OnRelease (1)
    MyButtonHandler::OnPress (2)
    MyButtonHandler::OnRelease (2)
    

    Run it at Wandbox.

    So there you go. Very different to yours (because you set off on the wrong track and there was no way back from there). I suggest you take it and run with it, best of luck.