The problem is that *ppv
is usually a void*
- directly assigning this
to it will simply take the existing this
pointer and give *ppv
the value of it (since all pointers can be cast to void*
).
This is not a problem with single inheritance because with single inheritance the base pointer is always the same for all classes (because the vtable is just extended for the derived classes).
However - for multiple inheritance you actually end up with multiple base pointers, depending on which 'view' of the class you're talking about! The reason for this is that with multiple inheritance you can't just extend the vtable - you need multiple vtables depending on which branch you're talking about.
So you need to cast the this
pointer to make sure that the compiler puts the correct base pointer (for the correct vtable) into *ppv
.
Here's an example of single inheritance:
class A {
virtual void fa0();
virtual void fa1();
int a0;
};
class B : public A {
virtual void fb0();
virtual void fb1();
int b0;
};
vtable for A:
[0] fa0
[1] fa1
vtable for B:
[0] fa0
[1] fa1
[2] fb0
[3] fb1
Note that if you have the B
vtable and you treat it like an A
vtable it just works - the offsets for the members of A
are exactly what you would expect.
Here's an example using multiple inheritance (using definitions of A
and B
from above) (note: just an example - implementations may vary):
class C {
virtual void fc0();
virtual void fc1();
int c0;
};
class D : public B, public C {
virtual void fd0();
virtual void fd1();
int d0;
};
vtable for C:
[0] fc0
[1] fc1
vtable for D:
@A:
[0] fa0
[1] fa1
[2] fb0
[3] fb1
[4] fd0
[5] fd1
@C:
[0] fc0
[1] fc1
[2] fd0
[3] fd1
And the actual memory layout for D
:
[0] @A vtable
[1] a0
[2] b0
[3] @C vtable
[4] c0
[5] d0
Note that if you treat a D
vtable as an A
it will work (this is coincidence - you can't rely on it). However - if you treat a D
vtable as a C
when you call c0
(which the compiler expects in slot 0 of the vtable) you'll suddenly be calling a0
!
When you call c0
on a D
what the compiler does is it actually passes a fake this
pointer which has a vtable which looks the way it should for a C
.
So when you call a C
function on D
it needs to adjust the vtable to point to the middle of the D
object (at the @C
vtable) before calling the function.