You are trying to solve what has now become infamous in the C++ community as the dreaded "Diamond of Death Problem". Bjarne Stroustroup, the creator of C++, gives a classic illustration of this problem.
class Base {
public:
virtual void Method1() = 0;
virtual void Method2() = 0;
};
class Base1 : public Base
{
public:
virtual void Method1();
virtual void Method2();
};
class Base2 : public Base
{
public:
virtual void Method1();
virtual void Method2();
}
class Concrete : public Base1, public Base2
{
virtual void Method1();
virtual void Method2();
}
Look at the above class hierarchy. As you rightly guessed, the compiler does not know what version of Method1() and Method2() should be taken into the definition of the class Concrete. Since both Base1 and Base2 have their own versions of Method1() and Method2(), the compiler has 2 options to pick these definitions and it just gets confused and starts throwing all those errors with the word "ambiguous" thrown all around the place.
The solution proposed by Bjarne Stroustroup for the problem is a subterfuge called "Virtual Inheritence". In effect, what you have to do is introduce the keyword virtual when you derive from the class "Base".
class Base1 : virtual public Base
{
public:
virtual void Method1();
virtual void Method2();
};
class Base2 : virtual public Base
{
public:
virtual void Method1();
virtual void Method2();
}
This tells the compiler that, though there are multiple copies of Base in Base1 and Base2, they should in effect point to the same version of Base.
This ensures that when you define:
class Concrete : public Base1, public Base2
{
}
"Concrete" gets only a single copy of "Base", which both "Base1" and "Base2" are pointing to, thus resolving the ambiguity for the compiler.
How does the compiler achieve this? Every class with virtual methods is associated with something called as virtual function table or vtable. The vtable has a pointer to all the virtual methods in that class. When the classes Base1 and Base2 are loaded, the vtables for these 2 classes hold a single pointer to the Base class. Similarly, when Concrete is loaded, the vtable for Concrete too points to the same single instance of Base, effectively ensuring that there is a single instance of Base.
There are points to note here when instantiating Concrete. You will have to explicitly call the constructor of Base, just like you would explicitly call the constructors of Base1 and Base2. Something like
Concrete() : Base(), Base1(), Base2()
{
}
Also, if there is an explicit call to the constructor of Base() in the constructors of Base1 and Base2, this would be skipped while instantiating Concrete and the constructor of Base will be invoked directly.