Reputation: 1028
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.
Upvotes: 2
Views: 1011
Reputation: 302673
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;
^
Upvotes: 7
Reputation: 39101
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.
You can avoid try to avoid this particular problem by restoring the original pointer via a dynamic_cast
:
delete dynamic_cast<void*>(p);
But do not rely on this. It is still Undefined Behaviour (see Barry's answer).
Upvotes: 8
Reputation: 484
The order is sort of relevant because the order of destructors is opposite of the declaration order. However, it is actually "lucky" that it even works for pa1, since deleting objects of abstact class type with non-virtual destructor causes undefined behaviour. One always needs to add a virtual destructor for abstract classes.
Upvotes: 0