Consider the following code:
class A1
{
virtual void a() = 0;
};
class A2
{
virtual int a(int x) = 0;
};
class B : public A1, public A2
{
void a() {}
int a(int x) { return x; }
};
int main()
{
A1* pa1;
pa1 = new B;
delete pa1;
A2* pa2;
pa2 = new B;
delete pa2;
return 0;
}
Classes A1 and A2 are just pure abstract, so multiple inheritance should do no harm. Now, the above code will cause a crash during destructor call, but what is peculiar, only for one object: pa2. The fix to this problem seems quite obvious - use virtual destructors ~A1() and ~A2(). However, there are still two questions:
Why the virtual destructors are necessary, since we do not have any data in any of these classes?
Why is the behavior different for pa1 and pa2? I have found that this is related to the order in which classes are placed on the parent list. If you changed it to:
class B : public A2, public A1
then
delete pa1;
would cause crash.
A possible and typical memory layout:
+-A1---+ | vptr | +------+ +-A2---+ | vptr | +------+ +-B------------------+ | +-A1---+ +-A2---+ | | | vptr | | vptr | | | +------+ +------+ | +--------------------+
vptr is a pointer that points to some information about the most-derived type, e.g. the virtual function table, RTTI etc. (see e.g. the Itanium C++ ABI vtable layout)
So, when you write A2* p = new B, you'll end up with:
+-B------------------+ | +-A1---+ +-A2---+ | | | vptr | | vptr | | | +------+ +------+ | +-----------^--------+ ^ | p | new B
When you now delete p;, this can cause trouble in the free store deallocator, since the address stored in p is not the same as the address you've received from the allocator (new B). This won't happen if you cast to A1, i.e. A1* p = new B, since there's no offset in this case.
Live example
You can avoid try to avoid this particular problem by restoring the original pointer via a dynamic_cast:
delete dynamic_cast<void*>(p);
Live example
But do not rely on this. It is still Undefined Behaviour (see Barry's answer).
From [expr.delete]:
In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.
Undefined behavior is undefined. The virtual destructor is necessary because the standard says so (see also dyp's answer)
Compiling with warnings also helps:
main.cpp: In function 'int main()':
main.cpp:22:12: warning: deleting object of abstract class type 'A1' which has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete pa1;
^
main.cpp:26:12: warning: deleting object of abstract class type 'A2' which has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete pa2;
^
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With