Проблема в том, что *ppv
обычно является void*
- непосредственное присвоение ему this
просто возьмет существующий указатель this
и даст *ppv
его значение (поскольку все указатели могут быть приведены к void*
).
Это не проблема с одиночным наследованием, потому что при одиночном наследовании базовый указатель всегда одинаков для всех классов (потому что vtable просто расширяется для производных классов).
Однако - для множественного наследования вы фактически получаете несколько базовых указателей, в зависимости от того, о каком «представлении» класса вы говорите! Причина этого заключается в том, что при множественном наследовании вы не можете просто расширить vtable - вам нужно несколько vtables в зависимости от того, о какой ветке вы говорите.
Так что вам нужно привести указатель this
, чтобы компилятор поместил правильный базовый указатель (для правильной таблицы) в *ppv
.
Вот пример одиночного наследования:
class A {
virtual void fa0();
virtual void fa1();
int a0;
};
class B : public A {
virtual void fb0();
virtual void fb1();
int b0;
};
vtable для A:
[0] fa0
[1] fa1
vtable для B:
[0] fa0
[1] fa1
[2] fb0
[3] fb1
Обратите внимание, что если у вас есть B
vtable и вы рассматриваете его как A
vtable, он просто работает - смещения для членов A
- это именно то, что вы ожидаете.
Вот пример использования множественного наследования (с использованием определений A
и B
сверху) (примечание: только пример - реализации могут отличаться):
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 для C:
[0] fc0
[1] fc1
vtable для D:
@A:
[0] fa0
[1] fa1
[2] fb0
[3] fb1
[4] fd0
[5] fd1
@C:
[0] fc0
[1] fc1
[2] fd0
[3] fd1
А фактическое расположение памяти для D
:
[0] @A vtable
[1] a0
[2] b0
[3] @C vtable
[4] c0
[5] d0
Обратите внимание, что если вы рассматриваете D
vtable как A
, он будет работать (это совпадение - вы не можете на него полагаться). Однако - если вы обращаетесь к D
vtable как к C
при вызове c0
(что компилятор ожидает в слоте 0 vtable), вы внезапно будете вызывать a0
!
Когда вы вызываете c0
на D
, то, что делает компилятор, он фактически передает фальшивый указатель this
, который имеет vtable, который выглядит так, как и для C
.
Поэтому, когда вы вызываете функцию C
для D
, ей нужно настроить виртуальную таблицу так, чтобы она указывала на середину объекта D
(в таблице @C
) перед вызовом функции.