I think that I can explain this one... there is a better explanation somewhere in one of either Meyer's or Sutter's books, but I didn't feel like searching. I believe what you are seeing is a consequence of how virtual functions are implemented (vtables) and the "you don't pay for it until you use it" nature of C++.
If there are no virtual methods in use, then a pointer to the object points to the object's data. As soon as a virtual method is introduced, the compiler inserts a virtual lookup table (vtable) and the pointer points to this instead. I'm probably missing something (and my brain isn't working yet) since I could not get this to occur until I inserted a data member in the base class. If the base class has a data member and the first child class has a virtual, then the offsets differ by the size of the vtable (4 on my compiler). Here's an example that shows this clearly:
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
The interesting thing is that both bObj
and cObj
exhibit the address change as between calling methods on A
and B
or C
. The difference is that B
contains a virtual method. This allows the compiler to insert the additional table necessary to implement function virtualization. The other interesting thing that this program shows is that typeid(T)
and typeid(*ptr)
is different in B::x
when it is called virtually. You can also see a size increase using sizeof
as soon as the virtual table is inserted.
In your case, as soon as you made CWaitable::WakeWaiters
virtual, the vtable is inserted and it actually pays attention to real type of the object as well as inserting the bookkeeping structures necessary. This causes the offset to the base of the object to differ. I really wish that I could find the reference that describes a mythical memory layout and why the address of an object is dependent on the type that it is being interpreted as when inheritance is mixed into the fun.
General Rule: (and you have heard this before) base classes always have virtual destructors. This will help eliminate little surprises like this.