web-dev-qa-db-ja.com

C ++デストラクタはいつ呼び出されますか?

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

より具体的な質問:

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

2)質問1のフォローアップでは、オブジェクトがスコープ外に出るタイミングを定義します(オブジェクトが特定の{block}を離れるタイミングについてではありません)。つまり、言い換えると、リンクされたリスト内のオブジェクトでデストラクタが呼び出されるのはいつですか?

3)デストラクタを手動で呼び出したいですか?

96
Pat Murray

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");
}
64
David Schwartz

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

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

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

それを管理するために、vectorが内部で行うことは、Allocatorオブジェクトを介してrawメモリを割り当てます(特に指定しない限り、::operator newを使用することを意味します)。次に、(たとえば)Push_backを使用してvectorにアイテムを追加すると、ベクターは内部でplacement newを使用して、メモリスペースの(以前の)未使用部分にアイテムを作成します。

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

これを行うには、deleteを使用してデストラクタnotを明示的に呼び出して、ベクトルがメモリ内のオブジェクトを直接破棄します。

永続的に、誰かが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;
    }

    // release the buffer:
~circular_buffer() { operator delete(data); }
};

#endif

標準コンテナとは異なり、これはoperator newおよびoperator deleteを直接使用します。実際の使用では、おそらくアロケータークラスを使用する必要がありますが、現時点では、貢献するよりも注意をそらすための方が多くなります(とにかく)。

14
Jerry Coffin
  1. newを使用してオブジェクトを作成する場合、deleteを呼び出す必要があります。 make_sharedを使用してオブジェクトを作成すると、結果のshared_ptrはカウントを保持し、使用カウントがゼロになったときにdeleteを呼び出します。
  2. 範囲外になるということは、ブロックを離れることを意味します。これは、オブジェクトがnotnew(つまり、スタックオブジェクト)で割り当てられたと仮定して、デストラクタが呼び出されるときです。
  3. デストラクタを明示的に呼び出す必要があるのは、 placement new でオブジェクトを割り当てるときだけです=。
5
dasblinkenlight

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

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

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

4
Nathaniel Ford

質問3に詳細な答えを出すために、はい、特にdasblinkenlightが観察しているように、デストラクタを明示的に呼び出すことは(まれに)場合があります。

これの具体例を示します:

#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;
}

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

3
Stuart Golodetz
  1. Pointers-通常のポインターはRAIIをサポートしていません。 deleteを明示的に指定しないと、ゴミが発生します。幸いなことに、C++には 自動ポインタ があり、これを処理してくれます!

  2. Scope-プログラムに対して変数がinvisibleになるときを考えてください。あなたが指摘するように、通常これは{block}の終わりにあります。

  3. 手動破壊-これを試みないでください。スコープとRAIIに魔法をかけてください。

2
chrisaycock

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

1
cloudygoose

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

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


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

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

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

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

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

0
wyx

オブジェクトのコンストラクタは、メモリがそのオブジェクトに割り当てられた直後に呼び出され、デストラクタはそのオブジェクトのメモリの割り当てを解除する直前に呼び出されることに注意してください。

0
Sunny Khandare

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

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

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

  3. 私はそれを疑いますが、奇妙な状況がそこにある場合、私は驚かないでしょう。

0
tnecniv