myślę, że mogę wyjaśnić tę jedną ... tam jest lepsze wyjaśnienie gdzieś w jednej albo Meyer lub książki Suttera, ale nie miałem ochoty szukać. Wierzę, że to, co widzisz, jest konsekwencją implementacji funkcji wirtualnych (vtables) i "nie płacisz za to, dopóki nie użyjesz" natury C++.
Jeśli nie są używane żadne metody wirtualne, wskaźnik do obiektu wskazuje dane obiektu. Natychmiast po wprowadzeniu metody wirtualnej kompilator wstawia wirtualną tabelę odnośników (vtable), a wskaźnik wskazuje na to. Prawdopodobnie brakuje mi czegoś (a mój mózg jeszcze nie działa), ponieważ nie mogłem tego zrobić, dopóki nie wstawiłem elementu danych w klasie bazowej. Jeśli klasa podstawowa ma element danych, a pierwsza klasa potomna ma wartość wirtualną, wówczas przesunięcia różnią się rozmiarem tabeli vtable (4 w moim kompilatorze).Oto przykład, który pokazuje to jasno:
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;
}
Running to na moim komputerze (MacBook Pro) drukuje następujące:
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
Interesującą rzeczą jest to, że zarówno bObj
i cObj
eksponat zmiana adresu między metody wywoływania na A
i B
lub C
. Różnica polega na tym, że B
zawiera metodę wirtualną. Dzięki temu kompilator może wstawić dodatkową tabelę niezbędną do wdrożenia wirtualizacji funkcji. Inną interesującą rzeczą, którą pokazuje ten program, jest to, że typeid(T)
i typeid(*ptr)
jest inny w B::x
, gdy jest nazywany wirtualnie. Możesz również zobaczyć wzrost rozmiaru przy użyciu sizeof
natychmiast po wstawieniu wirtualnej tabeli.
W twoim przypadku, gdy tylko utworzysz CWaitable::WakeWaiters
wirtualny, zostanie wstawiony vtable, który faktycznie zwraca uwagę na rzeczywisty typ obiektu, jak również wstawienie niezbędnych struktur księgowych. Powoduje to, że przesunięcie do podstawy obiektu się różni. Naprawdę chciałbym móc znaleźć odniesienie, które opisuje mityczny układ pamięci i dlaczego adres obiektu zależy od typu, który jest interpretowany, gdy dziedziczenie jest mieszane w zabawę.
Zasada ogólna: (a słyszeliście to już wcześniej) klasy bazowe zawsze mają destruktory wirtualne. Pomoże to wyeliminować takie niespodzianki.
Krojenie odbywa się na obiektach, a nie na wskaźnikach. Kod, który wysłałeś, działa po drobnym poprawieniu. Opublikuj jakiś prawdziwy kod - moja krystaliczna piłka nie działa dzisiaj. BTW: tak naprawdę nie zamierzasz tego "omawiać", prawda? – dirkgently
To prawie na pewno wielokrotne dziedziczenie - prawdopodobnie próbka kodu została nadmiernie przycięta. –
@Earwicker: Skąd otrzymałeś wiele dziedziczenia? – dirkgently