2011-01-06 15 views
1

私は派生クラスがその基本クラスへのポインタと型互換性があることを理解します。与えられたサンプルコードではnew barのオブジェクト構築が行われ、foo::foo()とそれに続くbar::bar()が呼び出されます。それぞれのコンストラクタでは、クラスメンバfoo::int *abar::int *bにリソースを割り当てています。ベースクラスへの派生クラス型の互換性はメモリリークの原因になりますか?

これで、構築されたオブジェクトを基本クラス型に初期化しています。 objでは、私は基本クラスのデストラクタを呼び出すことはできますが、派生クラスのデストラクタは呼び出すことはできません。では、この場合、派生クラスのリソースをどのように割り当て解除できますか?これはメモリーリークではありませんか?

#include <iostream> 
class foo 
{ 
    int *a; 
public: 
    foo() 
    { 
     a = new int[5]; 
     std::cout << "\n foo constructor" << std::endl; 
    } 
    ~foo() 
    { 
     std::cout << "\n foo destructor" << std::endl; 
     delete[] a; 
    } 
}; 

class bar : public foo 
{ 
    int *b; 
public: 
    bar() 
    { 
     b = new int[5]; 
     std::cout << "\n bar constructor" << std::endl; 
    } 
    ~bar() 
    { 
     std::cout << "\n bar destructor" << std::endl; 
     delete[] b; 
    } 
}; 

int main() 
{ 
    foo *obj = new bar; // Derived class object is type compatible with base class 

    delete obj; // Equivalent to obj->~foo(); 
    return 0; 
} 

ありがとうございます。

+1

実際のコードでは、リソースを管理するすべてのクラスに対して、ルール3を実行し、デストラクタ、コピーコンストラクタ、およびコピー代入演算子を宣言する必要があります。 –

+0

http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three "Rule of Three" – Mahesh

答えて

4

これは "仮想デストラクタ"のアイデアです。技術的に言えば、基本クラス型のポインタでオブジェクトを削除する場合は、その基本クラスのデストラクタを仮想にするか、結果が未定義である必要があります。デストラクタ仮想をマークすると、その意味は他の仮想関数とは異なります。 「派生クラスがこの振る舞いをオーバーライドする」という意味ではなく、「ベースクラスポインタを使用してこのオブジェクトを削除するときは、ベースコンストラクタを呼び出す前に派生したデストラクタを呼び出します。これはあなたが望むふるまいです。

一般に、サブクラス化を計画するクラスは、この種の問題を防ぐための仮想デストラクタが必要です。

3

あなたdeleteその基底クラスの1つにポインタを経由して、派生クラスのオブジェクト場合は、基底クラスのデストラクタvirtual宣言されなければならない、そうでない場合の動作は未定義です。

~foo()virtualと宣言されていれば、あなたはいいですね。 ~bar()が最初に呼び出され、次に~foo()が呼び出されます。

+0

@James McNellis - デストラクタ 'foo'をvirutalに保つことによって、デストラクタが呼び出されているのがわかります。しかし、それはなぜ以前の場合ではないのですか?ありがとう。 – Mahesh

+1

@Mahesh:元のコードでは、 '〜foo()'は仮想ではないので、デストラクタへの呼び出しは動的にはディスパッチされません(技術的には動作は未定義ですが、 'が呼び出され、'〜bar() 'は呼び出されません)。 '〜foo()'をvirtual宣言すると、デストラクタ呼び出しは動的にディスパッチされ、正しく定義された正しい動作が得られます。 –

+0

私は、メソッドが基本クラスで仮想宣言されている場合、派生クラスでも仮想メソッドであることを知っています。デストラクタにも同じことが当てはまりますか? –

0

はこれを行い、

virtual ~foo() 
{ 
    //your code 
} 

これはdelete *pFooを行うことは、派生クラスのデストラクタ(~bar())を呼び出すことを保証します。呼び出される順番は~bar()で、その後には~foo()となります。


また、あなたはまた、~bar()ために同じことを行う場合は、さらにから派生したくない場合は、それはそれくらい必要に応じてこのシナリオのためではないですが、それは、

virtual ~bar() 
{ 
    //your code 
} 

で良いだろうbarとそれを派生したクラスのためにbar*を使いたいと思っています。

+2

'〜foo()'が 'virtual'宣言されている場合、'〜bar() 'は' virtual'キーワードで宣言されているかどうかにかかわらず 'virtual'です。 –

+1

'〜bar()'の定義の前に明示的に 'virtual'を書くことは何も変更しません。'〜foo() 'が仮想であるために自動的にバーチャルであり、' baz' 'bar'、' ~baz() 'も自動的に仮想になります。それは良いドキュメンテーションです。 –

+0

@James McNellis - 私が 'bar'から新しいクラスをさらに引き出すのに同じ概念が適用されますか? – Mahesh

3

実際には未定義の動作です。

From Standard docs。 5.3.5。3オペランドの静的タイプは、そのダイナミック型、異なる場合、静的な型 オペランドの動的タイプのベース・クラスでなければならない第一の代替において

(オブジェクトの削除)、を削除し静的型は仮想デストラクタを持たなければならず、振る舞いは と定義されていません。 ......

+0

+1標準からの抜粋です。 – Nawaz

+0

上記の例では、 'obj'動的型ではありませんか? – Mahesh

+1

@Mahesh: '* obj'の静的型は' foo'です。 '* obj'の動的型は' bar'です。効果的には、静的型はコード内で指定された型です。ダイナミックタイプはオブジェクトの実際のタイプです。オリジナルのコードで示されているように、2つは異なる場合があります。 –

関連する問題