Я думаю, что могу объяснить это ... есть лучшее объяснение где-то в одной из книг Мейера или Саттера, но мне не хотелось искать. Я полагаю, что то, что вы видите, является следствием того, как реализованы виртуальные функции (vtables) и природы C ++ «вы не платите за это, пока не используете».
Если виртуальные методы не используются, указатель на объект указывает на данные объекта. Как только виртуальный метод введен, компилятор вставляет виртуальную таблицу поиска (vtable), и указатель указывает на это. Я, вероятно, что-то упускаю (и мой мозг еще не работает), поскольку я не мог этого добиться, пока не вставил элемент данных в базовый класс. Если базовый класс имеет член данных, а первый дочерний класс - виртуальный, то смещения отличаются размером виртуальной таблицы (4 на моем компиляторе). Вот пример, который ясно показывает это:
template <typename T>
void displayAddress(char const* meth, T const* ptr) {
std::printf("%s - this = %08lx\n", static_cast<unsigned long>(ptr));
std::printf("%s - typeid(T).name() %s\n", typeid(T).name());
std::printf("%s - typeid(*ptr).name() %s\n", typeid(*ptr).name());
}
struct A {
char byte;
void f() { displayAddress("A::f", this); }
};
struct B: A {
virtual void v() { displayAddress("B::v", this); }
virtual void x() { displayAddress("B::x", this); }
};
struct C: B {
virtual void v() { displayAddress("C::v", this); }
};
int main() {
A aObj;
B bObj;
C cObj;
std::printf("aObj:\n");
aObj.f();
std::printf("\nbObj:\n");
bObj.f();
bObj.v();
bObj.x();
std::printf("\ncObj:\n");
cObj.f();
cObj.v();
cObj.x();
return 0;
}
При запуске этого на моем компьютере (MacBook Pro) печатается следующее:
aObj:
A::f - this = bffff93f
A::f - typeid(T)::name() = 1A
A::f - typeid(*ptr)::name() = 1A
bObj:
A::f - this = bffff938
A::f - typeid(T)::name() = 1A
A::f - typeid(*ptr)::name() = 1A
B::v - this = bffff934
B::v - typeid(T)::name() = 1B
B::v - typeid(*ptr)::name() = 1B
B::x - this = bffff934
B::x - typeid(T)::name() = 1B
B::x - typeid(*ptr)::name() = 1B
cObj:
A::f - this = bffff930
A::f - typeid(T)::name() = 1A
A::f - typeid(*ptr)::name() = 1A
C::v - this = bffff92c
C::v - typeid(T)::name() = 1C
C::v - typeid(*ptr)::name() = 1C
B::x - this = bffff92c
B::x - typeid(T)::name() = 1B
B::x - typeid(*ptr)::name() = 1C
Интересно то, что и bObj
, и cObj
показывают изменение адреса между вызовами методов на A
и B
или C
. Разница в том, что B
содержит виртуальный метод. Это позволяет компилятору вставить дополнительную таблицу, необходимую для реализации функции виртуализации. Другая интересная вещь, которую демонстрирует эта программа, состоит в том, что typeid(T)
и typeid(*ptr)
отличаются в B::x
, когда она вызывается виртуально. Вы также можете увидеть увеличение размера, используя sizeof
, как только виртуальная таблица будет вставлена.
В вашем случае, как только вы сделали CWaitable::WakeWaiters
виртуальным, vtable вставляется, и он фактически обращает внимание на реальный тип объекта, а также вставляет необходимые структуры бухгалтерского учета. Это приводит к тому, что смещение к основанию объекта будет отличаться. Мне бы очень хотелось, чтобы я мог найти ссылку, которая описывает мифический макет памяти и почему адрес объекта зависит от типа, который он интерпретирует, например, когда наследование смешивается с забавой.
Общее правило: (и вы слышали это раньше) базовые классы всегда имеют виртуальные деструкторы . Это поможет устранить такие маленькие сюрпризы, как этот.