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


89

開発環境: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を呼び出すことができるように、これを理解する必要があると感じています。

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


5
最小限の完全なサンプルプログラムを含めるための+1。(sscce.org
Robᵩ

2
基本クラスに意図的に非仮想デストラクタがありますか?
Kerrek SB、2011

2
小さな観察; あなたは罪を犯しており、BaseClassデストラクタを仮想化していません。
Lyke

私の不完全なサンプルでごめんなさい。はい、これらのクラスオブジェクトを多態的に使用できるように、BaseClassには仮想デストラクタが必要です。
Smg

1
@Lyke:あなたはOKをだへのポインタベースを得削除するつもりはないことがわかっている場合だけでなく、私はちょうどあなたが得る、あなたがベースのメンバーが仮想作るのですか場合は、funnily ...必ず作っていたとしてもより多くのデストラクタ。
Kerrek SB、2011

回答:


73

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

基本的に:

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

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


明確な答えをありがとう。さて、私は関わりを持つことができますが、仮想継承のようなものにはあまり詳しくないので、もっと勉強する必要があります。
Smg

@Smg:仮想継承では、「仮想」継承クラスは、最も派生したオブジェクトの唯一の責任の下にあります。あなたが持っている場合つまり、struct B: virtual A当時とstruct C: B破棄ときに、B呼び出しができB::D1た呼び出しターンでA::D2して破壊したときにCあなたを呼び出しC::D1た呼び出しB::D2A::D2(注どのB::D2ませんAデストラクタたinvokeん)。このサブディビジョンで本当に驚くべきことは、3つのデストラクタの単純な線形階層ですべての状況を実際に管理できることです。
Matthieu M.

うーん、要点がよくわからなかったのかもしれません...最初のケース(Bオブジェクトの破棄)では、A :: D2の代わりにA :: D1が呼び出されると思いました。また、2番目のケース(Cオブジェクトの破棄)では、A :: D2の代わりにA :: D1が呼び出されます。私が間違っている?
Smg

A :: D1は、ここではAが最上位クラスではないため、呼び出されません。Aの仮想ベースクラス(存在する場合と存在しない場合がある)を破棄する責任はAに属していませんが、トップレベルクラスのD1またはD0に属しています。
bdonlan 2011

37

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

未担当別のクラスから継承を使用してそのクラスのオブジェクト扱う場合CTOR及びデストラクタが使用されるvirtualキーワードを現在のオブジェクトが「充電中でない」であるので、オブジェクトが構築または破壊する(完全なオブジェクトでない場合、仮想ベースオブジェクト)。このctorは、仮想ベースオブジェクトへのポインタを受け取り、それを格納します。

担当 ctorのとdtorsは、他のすべての場合、すなわち関与なし仮想継承がない場合のためのものです。クラスに仮想デストラクタがある場合、担当削除 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);

結果:

  • デストラクタのためのvtableのそれぞれのエントリfoobazおよびquuxそれぞれの点における電荷削除デストラクタ。
  • b1そして、b2により構成されbaz() 、担当呼び出して、foo(1) 担当
  • q1 そして q2によって構成されているquux() 担当低下、foo(2) 充電中baz() しないイン充電へのポインタとfooそれが以前に構成オブジェクト
  • q2担当者により破壊さ~auto_ptr() れたます。これは、仮想dtor ~quux() in-charge削除を呼び出し、~baz() not-in-charge~foo() in-charge、およびを呼び出しますoperator delete
  • q1in-chargein-chargeを呼び出す~quux() in-chargeによって破棄さます~baz() ~foo()
  • b2 破壊される ~auto_ptr() 電荷に仮想デストラクタを呼び出し、~baz() 担当削除を呼び出し、~foo() 担当及びoperator delete
  • b1破壊される~baz() の担当呼び出して、~foo() 担当

由来誰もがquuxその使用していない-担当 ctorのとデストラクタをし、作成の責任を取るfooオブジェクトを。

原則として、仮想ベースを持たないクラスでは、担当外のバリアントは必要ありません。その場合、担当のバリアントは時々統一と呼ばれることがあり、および/または担当との両方のシンボルないイン電荷は単一の実装にエイリアスされています。


分かりやすい例と併せて分かりやすくご説明いただきありがとうございます。仮想継承が関係する場合、仮想基本クラスオブジェクトを作成するのは、最も派生したクラスの責任です。最も派生したクラス以外の他のクラスについては、それらは非担当コンストラクターによって解釈されることになっているため、仮想基本クラスに触れません。
Smg

非常に明確な説明をありがとう。auto_ptrを使用せず、代わりにコンストラクターでメモリを割り当て、デストラクターで削除した場合はどうなるのかを明確にしたいと思いました。その場合、担当外/担当削除のデストラクタは2つしかありませんか?
nonenone

1
@bhavin、いいえ、設定はまったく同じです。デストラクタ用に生成されたコードは、常にオブジェクト自体とすべてのサブオブジェクトを破棄するためdelete、独自のデストラクタの一部として、またはサブオブジェクトデストラクタ呼び出しの一部として、式のコードを取得します。deleteそれは我々が見つけ仮想デストラクタ(持っている場合、式は、オブジェクトのvtableのを介してコールのいずれかとして実装されているの担当削除し、またはオブジェクトへの直接呼び出しとしてです担当。デストラクタ
サイモン・リヒター

delete式は呼び出すことはありませんではない-担当仮想継承を使用するオブジェクトを破壊しながら、他の唯一のデストラクタで使われている変異体を。
Simon Richter
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.