web-dev-qa-db-ja.com

GNU GCC(g ++):なぜ複数のdtorを生成するのですか?

開発環境:GNU GCC(g ++)4.1.2

ユニットテストで「コードカバレッジ-特に関数カバレッジ」を増やす方法を調査しようとしているときに、クラスdtorの一部が複数回生成されているように見えることがわかりました。何人かあなたはその理由について何か考えていますか?

次のコードを使用して、上記のことを試して観察しました。

「test.h」

class BaseClass
{
public:
    ~BaseClass();
    void someMethod();
};

class DerivedClass : public BaseClass
{
public:
    virtual ~DerivedClass();
    virtual void someMethod();
};

「test.cpp」

#include <iostream>
#include "test.h"

BaseClass::~BaseClass()
{
    std::cout << "BaseClass dtor invoked" << std::endl;
}

void BaseClass::someMethod()
{
    std::cout << "Base class method" << std::endl;
}

DerivedClass::~DerivedClass()
{
    std::cout << "DerivedClass dtor invoked" << std::endl;
}

void DerivedClass::someMethod()
{
    std::cout << "Derived class method" << std::endl;
}

int main()
{
    BaseClass* b_ptr = new BaseClass;
    b_ptr->someMethod();
    delete b_ptr;
}

上記のコード(g ++ test.cpp -o test)をビルドして、次のように生成されたシンボルの種類を確認したところ、

nm --demangleテスト

次の出力が表示されました。

==== following is partial output ====
08048816 T DerivedClass::someMethod()
08048922 T DerivedClass::~DerivedClass()
080489aa T DerivedClass::~DerivedClass()
08048a32 T DerivedClass::~DerivedClass()
08048842 T BaseClass::someMethod()
0804886e T BaseClass::~BaseClass()
080488f6 T BaseClass::~BaseClass()

私の質問は次のとおりです。

1)なぜ複数のdtorが生成されたのですか(BaseClass-2、DerivedClass-3)?

2)これらのdtorの違いは何ですか?これらの複数のdtorはどのように選択的に使用されますか?

C++プロジェクトの関数カバレッジを100%にするには、これを理解して、ユニットテストでこれらのすべてのdtorを呼び出すことができるようになっていると感じています。

上記について回答を頂ければ幸いです。

88
Smg

まず、これらの関数の目的は Itanium C++ ABI で説明されています。 「ベースオブジェクトデストラクタ」、「完全なオブジェクトデストラクタ」、および「デストラクタの削除」の定義を参照してください。マングル名へのマッピングは、5.1.4に示されています。

基本的に:

  • D2は「ベースオブジェクトデストラクタ」です。オブジェクト自体、およびデータメンバーと非仮想基本クラスを破棄します。
  • D1は「完全なオブジェクトデストラクタ」です。さらに、仮想基本クラスを破棄します。
  • D0は「削除オブジェクトデストラクタ」です。完全なオブジェクトデストラクタが行うすべての処理を実行し、さらにoperator deleteを呼び出して実際にメモリを解放します。

仮想基本クラスがない場合、D2とD1は同一です。 GCCは、十分な最適化レベルで、実際には両方のシンボルを同じコードにエイリアスします。

69
bdonlan

通常、コンストラクターには2つのバリアント(not-in-charge/in-charge)と3つのデストラクタ(not-in-charge/in-charge)があります。/担当削除)。

not-in-charge ctorおよびdtorは、virtualキーワードを使用して別のクラスから継承するクラスのオブジェクトを処理するときに、オブジェクトが完全なオブジェクトではない場合(つまり、現在のオブジェクト)に使用されます仮想ベースオブジェクトの作成または破棄は「担当していません」)。このctorは、仮想ベースオブジェクトへのポインタを受け取り、それを格納します。

担当のctorとdtorは、他のすべてのケース、つまり仮想継承が含まれていない場合のものです。クラスに仮想デストラクタがある場合、担当削除 dtorポインタはvtableスロットに移動しますが、オブジェクトの動的タイプ(つまり、自動または静的ストレージ期間を持つオブジェクト)を認識するスコープは、 担当 dtor(このメモリを解放してはならないため)。

コード例:

struct foo {
    foo(int);
    virtual ~foo(void);
    int bar;
};

struct baz : virtual foo {
    baz(void);
    virtual ~baz(void);
};

struct quux : baz {
    quux(void);
    virtual ~quux(void);
};

foo::foo(int i) { bar = i; }
foo::~foo(void) { return; }

baz::baz(void) : foo(1) { return; }
baz::~baz(void) { return; }

quux::quux(void) : foo(2), baz() { return; }
quux::~quux(void) { return; }

baz b1;
std::auto_ptr<foo> b2(new baz);
quux q1;
std::auto_ptr<foo> q2(new quux);

結果:

  • foobazquuxの各vtableのdtorエントリは、それぞれの担当削除 dtorを指しています。
  • b1およびb2は、baz()in-chargeによって構築され、foo(1)in-chargeを呼び出します
  • q1およびq2は、quux()in-chargeによって構成され、foo(2)in-chargeおよびbaz()になります。 担当外以前に構築したfooオブジェクトへのポインター
  • q2は、~auto_ptr()担当によって破壊され、仮想DTORを呼び出します~quux()担当削除~baz()を呼び出します担当外~foo()担当およびoperator delete
  • q1~quux()in-chargeによって破壊され、~baz()not-in-chargeおよび~foo()in-を呼び出します充電
  • b2は、~auto_ptr()担当によって破壊され、仮想DTORを呼び出します~baz()担当削除~foo()を呼び出します担当およびoperator delete
  • b1は、~baz()担当によって破壊され、~foo()担当を呼び出します

quuxから派生する人は誰でも、そのnot-in-charge ctorおよびdtorを使用し、fooオブジェクトを作成する責任を負います。

原則として、not-in-chargeバリアントは、仮想ベースを持たないクラスには必要ありません。その場合、担当バリアントは統一と呼ばれることもあり、/担当非担当の両方のシンボルは単一の実装。

37
Simon Richter