Я думаю, что я могу объяснить этот ... есть лучшее объяснение где-то в одном из либо Мейера или книги Саттера, но я не хотел искать. Я считаю, что то, что вы видите, является следствием реализации виртуальных функций (vtables) и «вы не платите за нее, пока не используете ее», характер C++.
Если виртуальных методов не используется, указатель на объект указывает на данные объекта. Как только виртуальный метод вводится, компилятор вставляет таблицу виртуального поиска (vtable), и вместо этого указатель указывает на это. Я, вероятно, что-то пропустил (и мой мозг пока не работает), так как я не мог это сделать, пока не вставил элемент данных в базовый класс. Если базовый класс имеет элемент данных, а первый дочерний класс имеет виртуальный, то смещения отличаются размером 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, и на самом деле она обращает внимание на реальный тип объекта, а также на вставку необходимых бухгалтерии. Это приводит к тому, что смещение базы объекта будет отличаться. Я действительно хочу, чтобы я мог найти ссылку, описывающую мифический макет памяти, и почему адрес объекта зависит от типа, который он интерпретируется, когда наследование смешивается с потерей.
Общее правило: (и вы слышали это раньше) базовые классы всегда имеют виртуальные деструкторы. Это поможет устранить небольшие сюрпризы, подобные этому.
Нарезка происходит на объектах, а не на указателях. Код, который вы опубликовали, работает после небольшого исправления. Отправьте некоторый реальный код - мой кристаллический шар не работает сегодня. Кстати: вы действительно не собираетесь проходить «это», не так ли? – dirkgently
Это почти наверняка многократное наследование - образец кода, вероятно, был обрезан. –
@Earwicker: Откуда вы получили множественное наследство? – dirkgently