I have the code:
class A1 {
public:
A1() { cout << "A1"; }
virtual ~A1() { cout << "~A1"; }
};
class A2 {
public:
A2() { cout << "A2"; }
~A2() { cout << "~A2"; }
};
class B : public A1, A2 {
public:
B() { cout << "B"; }
~B() { cout << "~B"; }
};
int main() {
A1* pa1 = new B;
delete pa1;
return 0;
}
It works totally fine because I use a virtual destructor. Output: A1A2B~B~A2~A1
as expected.
Then I remove virtual
:
class A1 {
public:
A1() { cout << "A1"; }
~A1() { cout << "~A1"; }
};
class A2 {
public:
A2() { cout << "A2"; }
~A2() { cout << "~A2"; }
};
class B : public A1, A2 {
public:
B() { cout << "B"; }
~B() { cout << "~B"; }
};
int main() {
A1* pa1 = new B;
delete pa1;
return 0;
}
And it outputs this: A1A2B~A1
, which I don't understand. I thought it was supposed to output A1A2B
since ~B()
isn't going to run, and we don't have an instance of A1, so ~A1()
should not be invoked as well. And then, why only ~A1()
is running, without ~A2()
? Why does it behave like this?
Without virtual
the destructor is resolved statically, based on the declared type of the pointer. The compiler sees an A1 *
so delete pa1
will call the ~A1
destructor, and nothing else.
we don't have an instance of A1, so ~A1() should not be invoked as well.
B
is derived from A1
so an instance of A1
exists as a subobject of every B
. Otherwise A1* pa1 = new B;
would not even compile.
And then, why only ~A1() is running, without ~A2()? Why does it behave like this?
Because the pointer is declared as an A1 *
. That the actual object pointed to is a B
does not matter since the destructor is not virtual.
The same way, if you changed the A2
inheritance to public and declared A2 *pa2 = new B;
then delete pa2
would call ~A2
and nothing else.
[ EDIT ] The above attempts to answer the direct question of how the compiler resolves the destructor call. An important caveat, however, is that deleting a pointer to a derived type through the base pointer is technically UB (undefined behavior) when the destructor of the base class is not virtual. Quoting from the C++ standard 5.3.5/3:
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.
In practice, many implementations will allow non-virtual destructors to still "work" as expected in certain simple cases, such as single inheritance between non-polymorphic types. Might even work for pointers to the first base class in a multiple inheritance case (for example, the pa1
above). But it's still UB strictly speaking, and there are cases where it's almost guaranteed to fail, such as pointers to a base class other than the first one (see an example of one such error here).