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


118

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

より具体的な質問:

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

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

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


3
あなたの特定の質問でさえ、広すぎます。「そのポインタは後で削除される」と「指す新しいアドレスを指定する」はまったく異なります。さらに検索し(これらの一部は回答済みです)、見つからなかった部分について個別に質問します。
Matthew Flaschen、2012

回答:


74

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

2
あなたの最後の例は関数を宣言したと思いましたか?これは、「最も厄介な解析」の例です。(もう1つ重要な点はnew Foo()、大文字の「F」を意味していると思います。)
スチュアートゴロデッツ

1
Foo myfoo("foo")Most Vexing Parseではないと思いますが、そうchar * foo = "foo"; Foo myfoo(foo);です。
コサイン、2014年

それは愚かな質問かもしれませんが、delete myFoo前に呼ばれるべきではありませんFoo *myFoo = new Foo("foo");か?それ以外の場合は、新しく作成されたオブジェクトを削除しますか?
Matheus Rocha、2016

何もありませんmyFoo前のFoo *myFoo = new Foo("foo");ライン。その行は、という新しい変数を作成し、myFoo既存の変数を隠します。この場合、myFoo上記は終了したの範囲内であるため、既存のものはありませんif
David Schwartz

1
@galactikuh「スマートポインタ」とは、オブジェクトへのポインタのように機能するものですが、そのオブジェクトの有効期間の管理を容易にする機能も備えています。
David Schwartz 2017

20

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

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

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

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

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

そのためには、ベクターは、直接、明示的にデストラクタを呼び出すことにより、メモリ内のオブジェクトを破壊しません、を使用するの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 newoperator deleteます。実際の使用では、おそらくアロケータクラスを使用する必要がありますが、現時点では、コントリビュートするよりも注意をそらすことが多くなります(IMO、とにかく)。


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

1
明白なポインターではないが、参照カウント(shared_ptr)があります。
Pubby

1
@Pubby:良い点、良い実践を推進しましょう。回答を編集しました。
MSalters 2012

6

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

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

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


3

質問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;
}

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


2
  1. ポインタ -通常のポインタはRAIIをサポートしていません。明示的deleteでなければ、ゴミがあります。幸い、C ++にはこれを処理する自動ポインターがあります。

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

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


注:リンクで言及されているように、auto_ptrは非推奨です。
tnecniv

std::auto_ptrはい、C ++ 11では非推奨です。OPが実際にC ++ 11を持っている場合はstd::unique_ptr、単一の所有者、またはstd::shared_ptr参照カウントされる複数の所有者に使用する必要があります。
chrisaycock 2012

「手動による破壊-決してこれを試みないでください」。コンパイラが理解できないシステムコールを使用して、別のスレッドへのオブジェクトポインタをキューに入れることがよくあります。scope / auto / smartポインターに「依存」すると、オブジェクトがコンシューマースレッドで処理される前に呼び出し元のスレッドによって削除されるため、アプリが壊滅的に失敗します。この問題は、スコープが制限され、refCountedのオブジェクトとインターフェースに影響します。ポインタと明示的な削除だけが行います。
マーティンジェームズ

@MartinJamesコンパイラが理解できないシステムコールの例を投稿できますか?そして、どのようにキューを実装していますか?ではないstd::queue<std::shared_ptr>?私は見つけたpipe()コピーがあまりにも高価でない場合は、生産者と消費者のスレッドメイクの同時実行の間に非常に簡単。
chrisaycock 2012

myObject = new myClass(); PostMessage(aHandle、WM_APP、0、LPPARAM(myObject));
マーティンジェームス

1

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


0

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

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

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

  3. 私はそれを疑いますが、そこに奇妙な状況があったとしても私は驚かないでしょう。


1
「最初にdeleteを呼び出さずにポインタを再割り当てすると、オブジェクトがメモリ内のどこかにまだ存在しているため、メモリリークが発生します。」必ずしも。別のポインタで削除された可能性があります。
Matthew Flaschen、2012

0

ポインタを介さずにオブジェクトが作成された場合(たとえば、A a1 = A();)、オブジェクトが破棄されたときにデストラクタが呼び出されます。

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


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

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

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

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

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


デストラクタを手動で呼び出せないのは事実ではありません-できます(たとえば、私の回答のコードを参照してください)。本当のことは、あなたがしてはいけない時間の大部分が:)
Stuart Golodetz

0

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.