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

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

person Omnifarious    schedule 15.06.2017    source источник
comment
Можете ли вы вставить фактический класс?   -  person Passer By    schedule 15.06.2017
comment
Многие компиляторы генерируют два разных деструктора для одного класса: один для уничтожения динамически размещенных объектов, другой — для уничтожения нединамических объектов (статических, локальных объектов или подобъектов). Первый вызывает operator delete изнутри, второй — нет. Некоторые компиляторы делают это, добавляя скрытый параметр к одному деструктору (так делают более старые версии GCC), некоторые компиляторы просто генерируют два отдельных деструктора (так делают более новые версии GCC). См., например, здесь что такое ветка в деструкторе, о которой сообщает gcov"> stackoverflow.com/questions/7199360/.   -  person AnT    schedule 15.06.2017
comment
@AnT Это звучит как ответ!   -  person Omnifarious    schedule 15.06.2017
comment
@PasserBy Я сделаю это, когда снова смогу использовать свой ноутбук. Хотя они чертовски просты.   -  person Omnifarious    schedule 15.06.2017


Ответы (2)


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

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

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

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

person AnT    schedule 15.06.2017
comment
Оба ваших ответа превосходны по разным причинам. - person Omnifarious; 15.06.2017
comment
Ваш ответ имеет наилучшие шансы на обучение кого-то с меньшим опытом. Я выберу это. - person Omnifarious; 15.06.2017

GCC использует Itanium ABI:

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

person user8163793    schedule 15.06.2017
comment
Оба ваших ответа превосходны по разным причинам. - person Omnifarious; 15.06.2017