Почему в выводе моей сборки есть две реализации деструктора?
А также objdump
моего файла.o показывает, что у меня есть два разных деструктора для одного и того же класса. Зачем?
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>
Это классы в заголовочном файле, которые приводят к генерации этого кода:
#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;
};
2 ответа
Многие компиляторы генерируют два разных деструктора для одного класса: один для уничтожения динамически размещаемых объектов, другой - для уничтожения нединамических объектов (статических объектов, локальных объектов, базовых дочерних объектов или дочерних дочерних объектов). Бывшие звонки operator delete
изнутри последний не делает. Некоторые компиляторы делают это, добавляя скрытый параметр к одному деструктору (более старые версии GCC делают это таким образом, MSVC++ делает это таким образом), некоторые компиляторы просто генерируют два отдельных деструктора (более новые версии GCC делают это таким образом).
Нужно позвонить operator delete
изнутри деструктор возникает из спецификации C++, которая говорит, что надлежащий operator delete
должен быть выбран "как если бы", он был найден изнутри (возможно, виртуального) деструктора самого производного объекта. Так, operator delete
, которая может быть реализована как статическая функция-член, должна вести себя как виртуальная функция.
Большинство реализаций реализуют это требование "буквально": они не только ищут правильные operator delete
изнутри деструктора, они на самом деле зовут его оттуда.
Конечно, operator delete
должен вызываться только из деструктора самого производного объекта, и только если этот объект был динамически размещен. Вот где этот скрытый параметр (или две версии деструктора) входит в картину.
Начиная с GCC 3.2, бинарные соглашения GCC для C++ основаны на написанном, независимом от производителя ABI-интерфейсе C++, который был разработан специально для 64-битного Itanium ...
Itanium ABI определяет различные деструкторы:
<ctor-dtor-name> ::= C1 # complete object constructor
::= C2 # base object constructor
::= C3 # complete object allocating constructor
::= D0 # deleting destructor
::= D1 # complete object destructor
::= D2 # base object destructor
Соглашение о количестве можно увидеть в выходных данных вашей сборки (разница между искажением имени в двух функциях равна 0 и 1).
Наконец, вот описание различия между этими двумя деструкторами:
Записи для виртуальных деструкторов на самом деле являются парами записей. Первый деструктор, называемый полным деструктором объекта, выполняет уничтожение, не вызывая delete() для объекта. Второй деструктор, называемый деструктором удаления, вызывает delete() после уничтожения объекта. Оба уничтожают любые виртуальные базы; отдельная, не виртуальная функция, называемая деструктором базового объекта, выполняет уничтожение объекта, но не его виртуальных базовых подобъектов, и не вызывает delete()
Кроме того, это происходит только в том случае, если в вашем классе есть виртуальный деструктор:
Этот ABI не требует генерации или использования выделения конструкторов или удаления деструкторов для классов без виртуального деструктора. Однако, если реализация испускает такие функции, она должна использовать внешние имена, указанные в этом ABI. Если такая функция имеет внешнюю связь, она должна генерироваться, где бы она ни использовалась, в группе COMDAT, имя которой является внешним именем функции.