そして、私の.oファイルのobjdump
は、同じクラスに2つの異なるデストラクタがあることを示しています。どうして?
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つのクラスに対して2つの異なるデストラクタを生成します。1つは動的に割り当てられたオブジェクトを破棄するため、もう1つは非動的オブジェクト(静的オブジェクト、ローカルオブジェクト、基本サブオブジェクト、またはメンバーサブオブジェクト)を破棄するためです。前者は内部からoperator delete
を呼び出しますが、後者は呼び出しません。一部のコンパイラは、1つのデストラクタに非表示パラメータを追加することによってそれを行います(古いバージョンのGCCはそのように行い、MSVC++はそのように行います)、一部のコンパイラは単に2つの別々のデストラクタを生成します(新しいバージョンのGCCはそのように行います)。
デストラクタの内部からoperator delete
を呼び出す必要性は、C++仕様から生じます。これは、適切なoperator delete
を、ほとんどの(おそらく仮想の)デストラクタの内部から検索したかのように選択する必要があることを示しています。派生オブジェクト。したがって、staticメンバー関数として実装できるoperator delete
は、virtual関数であるかのように動作する必要があります。
ほとんどの実装は、この要件を「文字通り」実装します。デストラクタの内部から適切なoperator delete
を検索するだけでなく、実際にはそこからcallそれを検索します。
もちろん、operator delete
は、最も派生したオブジェクトのデストラクタから呼び出す必要があり、そのオブジェクトが動的に割り当てられている場合に限ります。ここで、その非表示のパラメーター(または2つのバージョンのデストラクタ)が登場します。
GCC 3.2以降、C++のGCCバイナリ規則は、64ビットItaniumに固有になるように設計されたベンダー中立のC++ ABIに基づいています...
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
番号の規則は、アセンブリの出力で確認できます(2つの関数の名前マングリングの違いは0と1です)。
最後に、これら2つのデストラクタの違いについて説明します。
仮想デストラクタのエントリは、実際にはエントリのペアです。完全なオブジェクトデストラクタと呼ばれる最初のデストラクタは、オブジェクトに対してdelete()を呼び出さずに破棄を実行します。削除デストラクタと呼ばれる2番目のデストラクタは、オブジェクトを破棄した後にdelete()を呼び出します。どちらも仮想ベースを破壊します。ベースオブジェクトデストラクタと呼ばれる別の非仮想関数は、オブジェクトの破棄を実行しますが、仮想ベースサブオブジェクトは実行せず、delete()を呼び出しません。
さらに、これは、クラスに仮想デストラクタがある場合にのみ発生します。
このABIでは、仮想デストラクタのないクラスのコンストラクタを割り当てたり、デストラクタを削除したりする必要はありません。ただし、実装がそのような関数を発行する場合は、このABIで指定された外部名を使用する必要があります。そのような関数に外部リンケージがある場合、その名前が関数の外部名であるCOMDATグループ内で、参照される場所で発行する必要があります。