I think I can explain this ... there is a better explanation somewhere in one of Meyer's or Sutter's books, but I did not want to search. I believe that what you see is a consequence of the implementation of virtual functions (vtables) and "you do not pay for it until you use it," the nature of C ++.
If virtual methods are not used, then the pointer to the object points to the data of the object. Once the virtual method is introduced, the compiler inserts a virtual lookup table (vtable), and instead a pointer points to this. I probably missed something (and my brain is not working yet), since I could not do this until I inserted the data element into the base class. If the base class has a data element and the first child class has a virtual one, then the offsets differ in size vtable (4 in my compiler). Here is an example that clearly shows this:
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 this on my machine (MacBook Pro) prints the following:
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
Interestingly, both bObj and cObj display the change in address between the calling methods on A , either B or C The difference is that B contains a virtual method. This allows the compiler to insert an additional table needed to implement function virtualization. Another interesting thing this program shows is that typeid(T) and typeid(*ptr) differ in B::x when it is called virtually. You can also see the size increase with sizeof as soon as a virtual table is inserted.
In your case, once you have created a virtual CWaitable::WakeWaiters , the vtable is inserted and actually draws attention to the real type of the object, as well as the need to build in the necessary bookkeeping. This leads to the fact that the offset base of the object will be different. I really want so that I can find a link that describes a mythical memory layout and why the address of an object depends on the type that it is interpreted when, during inheritance, mixes in pleasure.
General rule: (and you've heard this before) base classes always have virtual destructors. This will help eliminate small surprises like this.