Here's why.
Consider the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
class Base1 {
int x;
public:
virtual void foo();
};
class Base2 {
int y;
public:
virtual void bar();
};
class Derived : public Base1, Base2 {
int z;
public:
virtual void baz();
};
| |
In memory, the layout of an instance of Derived probably looks something like:
MemAddr Contents
--------------------------------------------------------------------------------
0x10000000 vtable pointer for Derived
0x10000004 Derived::z
0x10000008 vtable pointer for Base1
0x1000000c Base1::x
0x10000010 vtable pointer for Base2
0x10000004 Base2::y
So
|
Derived* d = new Derived; // d has the address 0x10000000
| |
So, when the compiler needs to do a vtable lookup of Derived::baz, it
reads the vtable pointer from address 0x10000000, or, in other words,
(*d + 0 ).
And in fact, the compiler knows that given a pointer to an instance of an
object, the vtable pointer is always at offset 0. Furthermore, the first
data member of the object is always at offset 4 (assuming 32-bit vtable
pointer), etc.
But the multiple inheritance case breaks that. In the example above, to
call Base2::bar(), the compiler has to "fix up" the "this" pointer -- ie, d.
It needs to add 16 to it, and NOW the vtable pointer for Base2 is at
offset 0 and Base2::y is at offset 8 again.
The compiler has to do this because the compiler has to assume you
will instantiate Base2 directly, meaning that when it compiles the code
for Base2, it must assume the vtable pointer is at offset 0 and the first
data member, y, is at offset 4.
So why are you getting the compile error? Ultimately, it is because when
functions are called in Base1 and/or Base2 through an instance of D, the
compiler has to generate a "thunk" whose purpose is to "fix up" the "this"
pointer that will get passed to the member function.
At the point at which you call the member function by pointer, the compiler
cannot know which object contains the member function -- Derived, Base1,
or Base2, and therefore it does not know how to generate (or even if it
needs to generate) the fix up code.
(EDIT: My memory layout above may not be 100% accurate).