Search code examples
c++inheritanceevent-handlingvirtualmultiple-inheritance

Multiple Inheritance and Duplicate Function Calls


I have a situation similar to Multiple inheritance + virtual function mess. I've replicated the code in a less complex environment to demonstrate what I'm confused about.

I want to know how C is executing (but not acessing) both B1::method and B2::method, which then in turn executes the inherited methods.

The only way I can see this as working (correctly), is because the parent class is the one that propagates the function call to the sub classes, so it is accessing the vtables of Bx directly instead of through C.

In either case, is it safe, or undefined behavior, what pitfalls etc; and what is maybe a better way to do this.

#include <iostream>
#include <vector>

class A {
    static std::vector<A*> listeners;
public:
    static void propagate();
protected:
    A() {
        listeners.push_back(this);
    }
    ~A() {
        for (std::vector<A*>::iterator it = listeners.begin(); it != listeners.end(); ++it) {
            if (*it == this) {
                listeners.erase(it);
                break;
            }
        }
    }
    virtual void method()=0;
};

std::vector<A*> A::listeners;

void A::propagate() {
    for (unsigned int i=0; i < listeners.size(); ++i) {
        listeners[i]->method();
    }
}

class B1 : public A {
protected:
    B1() {}
    ~B1() {}
    void method() {
        B1inhmethod();
    }
    virtual void B1inhmethod() {}
};
class B2 : public A {
protected:
    B2() {}
    ~B2() {}
    void method() {
        B2inhmethod();
    }
    virtual void B2inhmethod() {}
};

class C : public B1, public B2 {
public:
    C() {}
    ~C() {}
    void B1inhmethod() {
        std::cout << "\nB1method in C";
    }
    void B2inhmethod() {
        std::cout << "\nB2method in C";
    }
};

int main() {
    C myclass;
    A::propagate();
    return 0;
}

output:

B1inhmethod in C
B2inhmethod in C

Solution

  • I think that the reason for this is that C inherits two copies of A, one from B1 and one from B2. The inheritance diagram looks like this:

    A       A
    |       |
    B1     B2
      \   /
       \ /
        C
    

    When you create C, it initializes both the B1 and B2 base classes, both of which recursively initialize their A base class. This causes two different pointers to be added into A's master list - the pointer to the B1 base object of C and the pointer to the B2 base object of A. These are both part of the same object - namely the C instance - but because there are logically two A base objects involved you'll get two pointers. When you then iterate over the list of A objects, you'll find C's B1 component and C's B2 component, and hence both the messages print out.

    If this isn't what you want, consider looking into virtual inheritance, which would let the B1 and B2 objects both share an A base object. That way, only one copy of the object would be added into the master list. Of course, you have to be careful if you do this, because then you would need C to have an implementation of method to avoid ambiguities; if you don't define it, there are two method implementations where neither is clearly the right one to call.