Почему у меня на выходе сборки есть две реализации деструктора?

И 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;
};

Ответ 1

Многие компиляторы генерируют два разных деструктора для одного класса: один для уничтожения динамически выделенных объектов, другой - для уничтожения нединамических объектов (статические объекты, локальные объекты, базовые под-объекты или членские под-объекты). Первый называет operator delete изнутри, а второй - нет. Некоторые компиляторы делают это, добавляя скрытый параметр в один деструктор (более старые версии GCC так делают, MSVС++ делает это именно так), некоторые компиляторы просто генерируют два отдельных деструктора (более новые версии GCC делают это именно так).

Необходимость вызова operator delete изнутри деструктора возникает из спецификации С++, в которой говорится, что правильный operator delete должен быть выбран "как если бы" он был просмотрен внутри (возможно, виртуального) деструктора самого производного объект. Таким образом, operator delete, который может быть реализован как статическая функция-член, должен вести себя так, как если бы это была виртуальная функция.

Большинство реализаций реализуют это требование "буквально": они не только ищут правильный operator delete изнутри деструктора, они фактически назовут его оттуда.

Конечно, operator delete нужно вызывать только из деструктора самого производного объекта, и только если этот объект был динамически распределен. Вот где этот скрытый параметр (или две версии деструктора) входят в изображение.

Ответ 2

GCC следует за Itanium ABI:

Начиная с GCC 3.2, двоичные соглашения GCC для С++ основаны на написанном, не зависящем от поставщика С++ ABI, который был разработан специально для 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, имя которой является внешним именем функции.