130

基本的な質問: プログラムが C++ でクラスのデストラクタ メソッドを呼び出すのはいつですか? オブジェクトがスコープ外になったり、delete

より具体的な質問:

1) オブジェクトがポインターを介して作成され、そのポインターが後で削除されるか、指す新しいアドレスが与えられた場合、それが指していたオブジェクトはそのデストラクタを呼び出しますか (他に何も指していないと仮定します)?

2) 質問 1 のフォローアップ。オブジェクトがいつスコープ外になるかを定義するもの (オブジェクトが特定の {block} をいつ離れるかは関係ありません)。つまり、リンク リスト内のオブジェクトに対してデストラクタが呼び出されるのはいつでしょうか。

3) デストラクタを手動で呼び出したいと思ったことはありませんか?

4

10 に答える 10

79

1) オブジェクトがポインタを介して作成され、そのポインタが後で削除されるか、指す新しいアドレスが与えられた場合、それが指していたオブジェクトはそのデストラクタを呼び出しますか (他に何も指していないと仮定します)?

ポインタの種類によって異なります。たとえば、スマート ポインターは、オブジェクトが削除されると、そのオブジェクトを削除することがよくあります。通常のポインタはそうではありません。ポインターが別のオブジェクトを指すように作成された場合も同様です。一部のスマート ポインターは、古いオブジェクトを破棄するか、参照がなくなった場合に破棄します。通常のポインターには、そのようなスマートはありません。それらはアドレスを保持するだけであり、具体的にそうすることで、それらが指すオブジェクトに対して操作を実行できます。

2) 質問 1 のフォローアップ。オブジェクトがいつスコープ外になるかを定義するもの (オブジェクトが特定の {block} をいつ離れるかは関係ありません)。つまり、リンク リスト内のオブジェクトに対してデストラクタが呼び出されるのはいつでしょうか。

それは、リンクされたリストの実装次第です。典型的なコレクションは、破棄されると、含まれているすべてのオブジェクトを破棄します。

したがって、ポインターのリンクされたリストは通常​​、ポインターを破棄しますが、ポインターが指すオブジェクトは破棄しません。(これは正しいかもしれません。それらは他のポインターによる参照である可能性があります。)ただし、ポインターを含むように特別に設計されたリンク リストは、それ自体の破棄時にオブジェクトを削除する可能性があります。

スマート ポインターのリンクされたリストは、ポインターが削除されたときにオブジェクトを自動的に削除するか、参照がなくなった場合にオブジェクトを削除します。あなたが望むことをする作品を選ぶのはあなた次第です.

3) デストラクタを手動で呼び出したいと思ったことはありませんか?

もちろん。1 つの例は、オブジェクトを同じ型の別のオブジェクトに置き換えたいが、再度割り当てるためだけにメモリを解放したくない場合です。古いオブジェクトをその場で破棄し、新しいオブジェクトをその場で構築できます。(ただし、一般的にこれは悪い考えです。)

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}
于 2012-04-10T00:15:14.560 に答える
23

他の人はすでに他の問題に対処しているので、1 つの点だけを見ていきます。オブジェクトを手動で削除したいと思ったことはありませんか。

答えはイエスです。@DavidSchwartz は 1 つの例を挙げましたが、かなり珍しいものです。多くの C++ プログラマーが常に使用しているものの内部にある例を示しますstd::vector(そしてstd::deque、あまり使用されていませんが)。

ほとんどの人が知っているようにstd::vector、現在の割り当てよりも多くのアイテムを追加すると、より大きなメモリ ブロックが割り当てられます。ただし、これを行うと、現在ベクトルにあるよりも多くのオブジェクトを保持できるメモリ ブロックが作成されます。

それを管理するために、内部で行うことは、オブジェクトを介してメモリをvector割り当てることです (特に指定しない限り、それは を使用することを意味します)。次に、(たとえば)を使用して に項目を追加すると、内部的にベクトルは を使用して、そのメモリ空間の (以前は) 未使用の部分に項目を作成します。Allocator::operator newpush_backvectorplacement new

eraseでは、ベクトルからアイテムを取得した場合はどうなりますか? ただ使用することはできませんdelete-- メモリのブロック全体を解放します。そのメモリ内の 1 つのオブジェクトを他のオブジェクトを破壊せずに破壊するか、それが制御するメモリ ブロックを解放する必要があります (たとえば、eraseベクトルから 5 つのアイテムを取得した場合、すぐにpush_backさらに 5 つのアイテムを追加すると、ベクトルが再割り当てされないことが保証されます)。あなたがそうするときの記憶。

そのために、ベクターは、 を使用するのではなく、デストラクタを明示的に呼び出して、メモリ内のオブジェクトを直接破棄しますdelete

ひょっとすると、他の誰かが連続したストレージを使って大まかにvector(またはstd::deque実際にそうしているように) コンテナーを作成した場合、ほぼ確実に同じ手法を使用したいと思うでしょう。

例として、循環リング バッファーのコードを記述する方法を考えてみましょう。

#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC

template <class T>
class circular_buffer {
    T *data;
    unsigned read_pos;
    unsigned write_pos;
    unsigned in_use;
    const unsigned capacity;
public:
    circular_buffer(unsigned size) :
        data((T *)operator new(size * sizeof(T))),
        read_pos(0),
        write_pos(0),
        in_use(0),
        capacity(size)
    {}

    void push(T const &t) {
        // ensure there's room in buffer:
        if (in_use == capacity) 
            pop();

        // construct copy of object in-place into buffer
        new(&data[write_pos++]) T(t);
        // keep pointer in bounds.
        write_pos %= capacity;
        ++in_use;
    }

    // return oldest object in queue:
    T front() {
        return data[read_pos];
    }

    // remove oldest object from queue:
    void pop() { 
        // destroy the object:
        data[read_pos++].~T();

        // keep pointer in bounds.
        read_pos %= capacity;
        --in_use;
    }
  
~circular_buffer() {
    // first destroy any content
    while (in_use != 0)
        pop();

    // then release the buffer.
    operator delete(data); 
}

};

#endif

標準のコンテナーとは異なり、これはoperator newand をoperator delete直接使用します。実際の使用では、おそらくアロケーター クラスを使用したいと思うでしょうが、現時点では、貢献するよりも気を散らすためのものです (とにかく)。

于 2012-04-10T02:20:31.837 に答える
10
  1. でオブジェクトを作成するときはnew、 を呼び出す必要がありますdelete。でオブジェクトを作成するとmake_shared、結果はカウントを保持し、使用カウントがゼロになったときにshared_ptr呼び出す責任があります。delete
  2. 範囲外に出るということは、ブロックを離れることを意味します。これは、オブジェクトが割り当てられていないnew(つまり、スタック オブジェクトである) と仮定して、デストラクタが呼び出されるときです。
  3. デストラクタを明示的に呼び出す必要があるのは、オブジェクトを配置newで割り当てるときだけです。
于 2012-04-10T00:14:33.737 に答える
6

1) オブジェクトは「ポインターを介して」作成されません。「新しい」オブジェクトに割り当てられるポインターがあります。これがあなたの言いたいことであると仮定すると、ポインターで「delete」を呼び出すと、ポインターが逆参照するオブジェクトが実際に削除されます(そしてデストラクタが呼び出されます)。ポインタを別のオブジェクトに割り当てると、メモリ リークが発生します。C++ では、ガベージを収集するものは何もありません。

2) これらは 2 つの別個の質問です。変数が宣言されているスタック フレームがスタックからポップされると、変数はスコープ外になります。通常、これはブロックを離れるときです。ヒープ内のオブジェクトが範囲外になることはありませんが、スタック上のポインターは範囲外になる可能性があります。リンク リスト内のオブジェクトのデストラクタが呼び出されることを保証するものは特にありません。

3) そうでもない。そうでないことを示唆するディープ マジックがあるかもしれませんが、通常は、「new」キーワードと「delete」キーワードを一致させ、デストラクタに必要なすべてを入れて、適切にクリーンアップするようにします。これを行わない場合は、そのオブジェクトのリソースを手動でクリーンアップする方法について、クラスを使用しているすべての人に具体的な指示をデストラクタにコメントしてください。

于 2012-04-10T00:15:30.137 に答える
3

質問 3 に詳細な回答を与えるには: はい、dasblinkenlight が観察しているように、特にプレースメント new に対応するものとして、デストラクタを明示的に呼び出す (まれな) 場合があります。

これの具体例を挙げると:

#include <iostream>
#include <new>

struct Foo
{
    Foo(int i_) : i(i_) {}
    int i;
};

int main()
{
    // Allocate a chunk of memory large enough to hold 5 Foo objects.
    int n = 5;
    char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));

    // Use placement new to construct Foo instances at the right places in the chunk.
    for(int i=0; i<n; ++i)
    {
        new (chunk + i*sizeof(Foo)) Foo(i);
    }

    // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
    for(int i=0; i<n; ++i)
    {
        Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
        std::cout << foo->i << '\n';
        foo->~Foo();
    }

    // Deallocate the original chunk of memory.
    ::operator delete(chunk);

    return 0;
}

この種の目的は、メモリ割り当てをオブジェクト構築から切り離すことです。

于 2012-04-10T00:36:11.873 に答える
3
  1. ポインター-- 通常のポインターは RAII をサポートしていません。明示的な がないdeleteと、ガベージが発生します。幸いなことに、C++ にはこれを処理する自動ポインターがあります。

  2. スコープ-- 変数がプログラムから見えなくなるときを考えてみてください。{block}ご指摘のとおり、通常、これは の最後にあります。

  3. 手動による破棄-- 絶対に試みないでください。scope と RAII に魔法をかけてもらいましょう。

于 2012-04-10T00:14:05.277 に答える
1

「new」を使用するとき、つまりポインターにアドレスをアタッチするとき、またはヒープ上のスペースを要求するときはいつでも、それを「削除」する必要があります。
1.はい、何かを削除すると、デストラクタが呼び出されます。
2.連結リストのデストラクタが呼び出されると、そのオブジェクトのデストラクタが呼び出されます。ただし、ポインターの場合は、手動で削除する必要があります。3.スペースが「new」によって要求された場合。

于 2012-04-10T00:17:42.570 に答える
0

deleteはい、デストラクタ (別名 dtor) は、オブジェクトがスタック上にある場合、またはオブジェクトへのポインタを呼び出した場合に、オブジェクトがスコープ外になったときに呼び出されます。

  1. ポインターが削除さdeleteれると、dtor が呼び出されます。最初に呼び出さずにポインタを再割り当てするdeleteと、オブジェクトがまだメモリのどこかに存在するため、メモリ リークが発生します。後者の場合、dtor は呼び出されません。

  2. リンクされたリストの適切な実装では、リストが破棄されるときに、リスト内のすべてのオブジェクトの dtor が呼び出されます (リストを破棄するために何らかのメソッドを呼び出したか、リスト自体が範囲外になったため)。これは実装に依存します。

  3. 疑わしいですが、何か奇妙な状況があっても驚かないでしょう。

于 2012-04-10T00:18:08.207 に答える
0

オブジェクトがポインターを介さずに作成された場合 (たとえば、A a1 = A();)、オブジェクトが破棄されたとき、常にオブジェクトが存在する関数が終了したときに、デストラクタが呼び出されます。たとえば、次のようになります。

void func()
{
...
A a1 = A();
...
}//finish


デストラクタは、コードが実行されて「終了」するときに呼び出されます。

オブジェクトがポインターを介して作成された場合 (たとえば、A * a2 = new A();)、ポインターが削除されると (delete a2;)、デストラクタが呼び出されます。新しいアドレスを削除する前に、メモリ リークが発生します。それはバグです。

リンクされたリストでは、std::list<> を使用する場合、std::list<> がこれらすべてを終了しているため、デストラクターやメモリ リークを気にする必要はありません。自分で書いた連結リストでは、明示的にデストラクタを記述し、ポインタを削除する必要があります。そうしないと、メモリ リークが発生します。

デストラクタを手動で呼び出すことはめったにありません。システムに提供する機能です。

下手な英語でごめんなさい!

于 2012-04-10T00:33:38.607 に答える