Порядок членов vtable в классе не определен. Действительно, вы можете (и будете!) Находить элементы данных между указателями vtable. Если вы пишете COM, вы должны привести к любому интерфейсу, который вы хотите вернуть в QueryInterface
, прежде чем записать его в указатель, в который вы помещаете результат. То есть что-то вроде:
HRESULT QueryInterface(REFIID riid, LPVOID *ppvObject) {
if (*riid == IID_MY_INTERFACE) {
*ppvObject = static_cast<IMyInterface *>(this);
return S_OK;
} else if (*riid == IID_SOMETHING_ELSE) {
*ppvObject = static_cast<ISomethingElse *>(this);
return S_OK;
} else /* ... */
}
Компилятор позаботится о том, чтобы найти правильное смещение для интерфейса.
Что касается того, как это на самом деле работает, подумайте о том, что это означает - каждый из интерфейсов должен существовать как объект на некотором поддиапазоне смещений внутри объекта. Скажем, у вас есть иерархия классов, которая выглядит следующим образом:
class IA { virtual void foo() = 0; int x;};
class IB { virtual void bar() = 0; int y; };
class C : public IA, public IB { int bar; };
Ваш макет в памяти может выглядеть так:
00000000 vtable ptr for C
00000004 vtable ptr for Ia
00000008 int x
0000000b vtable ptr for Ib
00000010 int y
00000014 int bar
Здесь вы можете получить указатель на Ia, получив смещение 0x00000004
в классе или Ib в 0x0000000b
.
В некоторых случаях может быть возможно оптимизировать это далее:
00000000 vtable ptr for C and Ia
00000004 int x
00000008 vtable ptr for Ib
0000000b int y
00000010 int bar
Я не уверен, действительно ли Win32 C ++ ABI делает это. Если вы сделаете это, то таблица C vtable будет начинаться с тех же членов, что и таблица Ia, а затем добавит дополнительные в конце.