Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritance and polymorphism in C++

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:

  1. Why the virtual destructors are necessary, since we do not have any data in any of these classes?

  2. 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.

like image 873
Piotr G Avatar asked Sep 16 '26 02:09

Piotr G


2 Answers

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).

like image 150
dyp Avatar answered Sep 17 '26 21:09

dyp


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;
            ^
like image 21
Barry Avatar answered Sep 17 '26 22:09

Barry



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!