And objdump
of my .o file reveals that I have two different destructors for the same class. Why?
Disassembly of section .text._ZN1AD0Ev:
0000000000000000 <_ZN1AD0Ev>:
0: 53 push %rbx
1: be 00 00 00 00 mov $0x0,%esi
6: 48 89 fb mov %rdi,%rbx
9: 48 c7 07 00 00 00 00 movq $0x0,(%rdi)
10: ba 2c 00 00 00 mov $0x2c,%edx
15: bf 00 00 00 00 mov $0x0,%edi
1a: e8 00 00 00 00 callq 1f <_ZN1AD0Ev+0x1f>
1f: 48 89 df mov %rbx,%rdi
22: be 08 00 00 00 mov $0x8,%esi
27: 5b pop %rbx
28: e9 00 00 00 00 jmpq 2d <_ZN1AD0Ev+0x2d>
Disassembly of section .text._ZN1AD2Ev:
0000000000000000 <_ZN1AD1Ev>:
0: 48 c7 07 00 00 00 00 movq $0x0,(%rdi)
7: ba 2c 00 00 00 mov $0x2c,%edx
c: be 00 00 00 00 mov $0x0,%esi
11: bf 00 00 00 00 mov $0x0,%edi
16: e9 00 00 00 00 jmpq 1b <_ZN1AD1Ev+0x1b>
These are the classes in the header file that result in this code being generated:
#include <iostream>
class A {
public:
virtual ~A() {
::std::cout << "This destructor does something significant.\n";
}
};
class B : public A {
public:
inline virtual ~B() = 0;
};
B::~B() = default;
class C : public B {
public:
inline virtual ~C() = default;
};
Many compilers generate two different destructors for one class: one for destroying dynamically allocated objects, another - for destroying non-dynamic objects (static objects, local objects, base sub-objects or member sub-objects). The former calls operator delete
from inside, the latter doesn't. Some compilers do it by adding a hidden parameter to one destructor (older versions of GCC do it that way, MSVC++ does it that way), some compilers simply generate two separate destructors (newer versions of GCC do it that way).
The need to call operator delete
from inside the destructor arises from C++ specification, which says that the proper operator delete
should be chosen "as if" it was looked up from inside the (possibly virtual) destructor of the most derived object. So, operator delete
, which can be implemented as a static member function should behave as if it were a virtual function.
Most implementations implement this requirement "literally": they not only look up the proper operator delete
from inside the destructor, they actually call it from there.
Of course, operator delete
only has to be called from the most derived object's destructor, and only if that object was dynamically allocated. This is where that hidden parameter (or two versions of destructor) come into the picture.