web-dev-qa-db-ja.com

C ++でのオブジェクトの破壊

C++でオブジェクトが正確に破壊されるのはいつですか?それはどういう意味ですか?ガベージコレクターがないため、手動で破棄する必要がありますか?例外はどのように作用しますか?

(注:これは、 Stack OverflowのC++ FAQ へのエントリとなることを意味します。FAQこのフォームでは、 このすべてを開始したメタの投稿 がそれを行う場所になります。その質問に対する回答は C++チャットルーム 、最初にFAQアイデアが始まったので、あなたの答えはアイデアを思いついた人に読まれそうです。)

65
fredoverflow

次のテキストでは、スコープオブジェクトを区別します。破壊の時間は、そのスコープ(関数、ブロック、クラス、式)によって静的に決定され、動的オブジェクト 、その正確な破棄時間は一般に実行時までわかりません。

クラスオブジェクトの破壊セマンティクスはデストラクタによって決定されますが、スカラーオブジェクトの破壊は常にノーオペレーションです。具体的には、ポインター変数を破棄すると、not指示先が破棄されます。

スコープ付きオブジェクト

自動オブジェクト

自動オブジェクト(一般に「ローカル変数」と呼ばれる)は、制御フローが定義の範囲を離れると、定義の逆の順序で破棄されます。

_void some_function()
{
    Foo a;
    Foo b;
    if (some_condition)
    {
        Foo y;
        Foo z;
    }  <--- z and y are destructed here
}  <--- b and a are destructed here
_

関数の実行中に例外がスローされると、以前に構築されたすべての自動オブジェクトは、例外が呼び出し元に伝播される前に破棄されます。このプロセスはstack unwindingと呼ばれます。スタックのアンワインド中、前述の以前に構築された自動オブジェクトのデストラクタを例外として残すことはできません。それ以外の場合は、関数_std::terminate_が呼び出されます。

これは、C++で最も重要なガイドラインの1つにつながります。

デストラクタは決して投げないでください。

非ローカルの静的オブジェクト

mainの実行後、名前空間スコープで定義された静的オブジェクト(一般に「グローバル変数」と呼ばれる)および静的データメンバーは、定義の逆の順序で破棄されます。

_struct X
{
    static Foo x;   // this is only a *declaration*, not a *definition*
};

Foo a;
Foo b;

int main()
{
}  <--- y, x, b and a are destructed here

Foo X::x;           // this is the respective definition
Foo y;
_

異なる変換単位で定義された静的オブジェクトの構築(および破棄)の相対的な順序は未定義であることに注意してください。

例外が静的オブジェクトのデストラクタを離れると、関数_std::terminate_が呼び出されます。

ローカル静的オブジェクト

関数内で定義された静的オブジェクトは、制御フローが初めて定義を通過するとき(およびその場合)に構築されます。1 mainの実行後、逆の順序で破棄されます。

_Foo& get_some_Foo()
{
    static Foo x;
    return x;
}

Bar& get_some_Bar()
{
    static Bar y;
    return y;
}

int main()
{
    get_some_Bar().do_something();    // note that get_some_Bar is called *first*
    get_some_Foo().do_something();
}  <--- x and y are destructed here   // hence y is destructed *last*
_

例外が静的オブジェクトのデストラクタを離れると、関数_std::terminate_が呼び出されます。

1:これは非常に単純化されたモデルです。静的オブジェクトの初期化の詳細は、実際にははるかに複雑です。

基本クラスのサブオブジェクトとメンバーのサブオブジェクト

制御フローがオブジェクトのデストラクター本体を離れると、そのメンバーサブオブジェクト(「データメンバー」とも呼ばれる)は、定義の逆の順序で破壊されます。その後、その基本クラスのサブオブジェクトは、base-specifier-listの逆の順序で破棄されます。

_class Foo : Bar, Baz
{
    Quux x;
    Quux y;

public:

    ~Foo()
    {
    }  <--- y and x are destructed here,
};          followed by the Baz and Bar base class subobjects
_

Fooのサブオブジェクトのいずれかのconstructionの間に例外がスローされると、その例外が伝播される前に、以前に構築されたすべてのサブオブジェクトが破棄されます。一方、Fooオブジェクトは完全には構築されなかったため、Fooデストラクタはnotが実行されます。

デストラクタ本体は、データメンバ自体を破棄する責任を負わないことに注意してください。データメンバがオブジェクトの破棄時に解放する必要があるリソース(ファイル、ソケット、データベース接続、ミューテックス、ヒープメモリなど)のハンドルである場合にのみ、デストラクタを記述する必要があります。

配列要素

配列要素は降順に破壊されます。 n番目の要素のconstructionの間に例外がスローされた場合、要素n-1から0は、例外が伝播される前に破棄されます。

一時オブジェクト

クラス型のprvalue式が評価されると、一時オブジェクトが構築されます。 prvalue式の最も顕著な例は、T operator+(const T&, const T&)などの値によってオブジェクトを返す関数の呼び出しです。通常の状況では、prvalueを字句的に含む完全な式が完全に評価されると、一時オブジェクトは破棄されます。

___________________________ full-expression
              ___________  subexpression
              _______      subexpression
some_function(a + " " + b);
                          ^ both temporary objects are destructed here
_

上記の関数呼び出しsome_function(a + " " + b)は、より大きな式の一部ではないため、完全な式です(代わりに、式ステートメントの一部です)。したがって、部分式の評価中に構築されるすべての一時オブジェクトは、セミコロンで破棄されます。このような一時オブジェクトは2つあります。1つ目は最初の追加時に作成され、2つ目は2回目の追加時に作成されます。 2番目の一時オブジェクトは、最初のオブジェクトの前に破棄されます。

2番目の追加中に例外がスローされると、例外を伝播する前に最初の一時オブジェクトが適切に破棄されます。

ローカル参照がprvalue式で初期化される場合、一時オブジェクトの有効期間はローカル参照のスコープに拡張されるため、ぶら下がり参照は取得されません。

_{
    const Foo& r = a + " " + b;
                              ^ first temporary (a + " ") is destructed here
    // ...
}  <--- second temporary (a + " " + b) is destructed not until here
_

非クラス型のprvalue式が評価される場合、結果は一時オブジェクトではなくvalueになります。ただし、prvalueを使用して参照を初期化する場合、一時オブジェクトwillが構築されます。

_const int& r = i + j;
_

動的オブジェクトと配列

次のセクションでは、Xを破棄は「最初にXを破棄してから、基礎となるメモリを解放する」ことを意味します。同様に、create Xは「最初に十分なメモリを割り当ててからXを構築する」ことを意味します。

動的オブジェクト

_p = new Foo_を介して作成された動的オブジェクトは、_delete p_を介して破棄されます。 _delete p_を忘れると、リソースリークが発生します。これらはすべて未定義の動作につながるため、次のいずれかを実行しようとしないでください。

  • _delete[]_(角括弧に注意)、free、またはその他の方法で動的オブジェクトを破棄します
  • 動的オブジェクトを複数回破壊する
  • 動的オブジェクトが破棄された後にアクセスする

動的オブジェクトのconstructionの間に例外がスローされた場合、基になるメモリは例外が伝播される前に解放されます。 (オブジェクトが完全に構築されたことはないため、デストラクタはnotがメモリ解放の前に実行されます。)

動的配列

_p = new Foo[n]_を介して作成された動的配列は、_delete[] p_を介して破棄されます(角括弧に注意してください)。 _delete[] p_を忘れると、リソースリークが発生します。これらはすべて未定義の動作につながるため、次のいずれかを実行しようとしないでください。

  • deletefreeまたはその他の方法で動的配列を破棄します
  • 動的配列を複数回破壊します
  • 破棄された後、動的配列にアクセスします

N番目の要素のconstructionの間に例外がスローされると、要素n-1から0は降順に破棄され、基になるメモリが解放され、例外が伝播されます。

(一般に、動的配列では_std::vector<Foo>_よりも_Foo*_を好むはずです。これにより、正確で堅牢なコードの作成がはるかに簡単になります。)

参照カウントスマートポインター

複数の_std::shared_ptr<Foo>_オブジェクトによって管理される動的オブジェクトは、その動的オブジェクトの共有に関係する最後の_std::shared_ptr<Foo>_オブジェクトの破棄中に破棄されます。

(一般に、共有オブジェクトでは_std::shared_ptr<Foo>_よりも_Foo*_を好むはずです。これにより、正確で堅牢なコードを簡単に記述できます。)

81
fredoverflow

オブジェクトのデストラクタは、オブジェクトの寿命が終了して破棄されると自動的に呼び出されます。通常、手動で呼び出す必要はありません。

このオブジェクトを例として使用します。

class Test
{
    public:
        Test()                           { std::cout << "Created    " << this << "\n";}
        ~Test()                          { std::cout << "Destroyed  " << this << "\n";}
        Test(Test const& rhs)            { std::cout << "Copied     " << this << "\n";}
        Test& operator=(Test const& rhs) { std::cout << "Assigned   " << this << "\n";}
};

C++には3つの異なるタイプのオブジェクト(C++ 11では4つ)があり、オブジェクトのタイプはオブジェクトの寿命を定義します。

  • 静的ストレージ期間オブジェクト
  • 自動ストレージ期間オブジェクト
  • 動的ストレージ期間オブジェクト
  • (C++ 11)スレッドストレージ期間オブジェクト

静的ストレージ期間オブジェクト

これらは最も単純で、グローバル変数と同等です。これらのオブジェクトの寿命は(通常)アプリケーションの長さです。これらは(通常)mainに入る前に構築され、mainを終了した後(作成されるのとは逆の順序で)破棄されます。

Test  global;
int main()
{
    std::cout << "Main\n";
}

> ./a.out
Created    0x10fbb80b0
Main
Destroyed  0x10fbb80b0

注1:静的ストレージ期間オブジェクトには、他に2つのタイプがあります。

クラスの静的メンバー変数。

これらはすべての意味と目的において、寿命の観点からグローバル変数と同じです。

関数内の静的変数。

これらは、遅延して作成された静的ストレージ期間オブジェクトです。これらは最初の使用時に作成されます(C++ 11のスレッドセーフマナーで)。他の静的ストレージ期間オブジェクトと同様に、アプリケーションが終了すると破棄されます。

建設/破壊の順序

  • コンパイル単位内の構築の順序は明確に定義されており、宣言と同じです。
  • コンパイル単位間の構築の順序は未定義です。
  • 破壊の順序は、構築の順序とまったく逆です。

自動ストレージ期間オブジェクト

これらは最も一般的なタイプのオブジェクトであり、99%の時間を使用する必要があります。

これらは、自動変数の3つの主なタイプです。

  • 関数/ブロック内のローカル変数
  • クラス/配列内のメンバー変数。
  • 一時変数。

ローカル変数

関数/ブロックが終了すると、その関数/ブロック内で宣言されたすべての変数が破棄されます(作成の逆順)。

int main()
{
     std::cout << "Main() START\n";
     Test   scope1;
     Test   scope2;
     std::cout << "Main Variables Created\n";


     {
           std::cout << "\nblock 1 Entered\n";
           Test blockScope;
           std::cout << "block 1 about to leave\n";
     } // blockScope is destrpyed here

     {
           std::cout << "\nblock 2 Entered\n";
           Test blockScope;
           std::cout << "block 2 about to leave\n";
     } // blockScope is destrpyed here

     std::cout << "\nMain() END\n";
}// All variables from main destroyed here.

> ./a.out
Main() START
Created    0x7fff6488d938
Created    0x7fff6488d930
Main Variables Created

block 1 Entered
Created    0x7fff6488d928
block 1 about to leave
Destroyed  0x7fff6488d928

block 2 Entered
Created    0x7fff6488d918
block 2 about to leave
Destroyed  0x7fff6488d918

Main() END
Destroyed  0x7fff6488d930
Destroyed  0x7fff6488d938

メンバー変数

メンバー変数の寿命は、それを所有するオブジェクトにバインドされます。所有者の寿命が終了すると、すべてのメンバーの寿命も終了します。したがって、同じルールに従う所有者の寿命を調べる必要があります。

注:メンバーは、作成の逆の順序で常に所有者よりも前に破棄されます。

  • したがって、クラスメンバーの場合、宣言の順序で作成されます。
    そして宣言の逆の順序で破壊された
  • したがって、配列メンバーの場合、それらは0-> topの順序で作成されます
    そして逆の順序で破壊-> 0

一時変数

これらは、式の結果として作成されますが、変数に割り当てられないオブジェクトです。一時変数は、他の自動変数と同様に破棄されます。スコープの終わりが、statementの終わりであるというだけです(これは通常、「;」です)。

std::string   data("Text.");

std::cout << (data + 1); // Here we create a temporary object.
                         // Which is a std::string with '1' added to "Text."
                         // This object is streamed to the output
                         // Once the statement has finished it is destroyed.
                         // So the temporary no longer exists after the ';'

注:一時的なものの寿命を延ばすことができる状況があります。
しかし、これはこの単純な議論には関係ありません。このドキュメントがあなたにとって第二の性質であることを理解するまでに、それが一時的なものの寿命を延ばす前にあなたがしたいことではありません。

動的ストレージ期間オブジェクト

これらのオブジェクトには動的な寿命があり、newで作成され、deleteの呼び出しで破棄されます。

int main()
{
    std::cout << "Main()\n";
    Test*  ptr = new Test();
    delete ptr;
    std::cout << "Main Done\n";
}

> ./a.out
Main()
Created    0x1083008e0
Destroyed  0x1083008e0
Main Done

ガベージコレクションされた言語の開発者にとっては、これは奇妙に思えます(オブジェクトの寿命を管理する)。しかし、問題は見かけほど悪くはありません。 C++では、動的に割り当てられたオブジェクトを直接使用することはまれです。寿命を制御する管理オブジェクトがあります。

GCで収集された他のほとんどの言語に最も近いのは、std::shared_ptrです。これにより、動的に作成されたオブジェクトのユーザー数が追跡され、すべてのユーザーがなくなるとdeleteが自動的に呼び出されます(これは通常のJavaオブジェクトのより良いバージョンだと思います)。

int main()
{
    std::cout << "Main Start\n";
    std::shared_ptr<Test>  smartPtr(new Test());
    std::cout << "Main End\n";
} // smartPtr goes out of scope here.
  // As there are no other copies it will automatically call delete on the object
  // it is holding.

> ./a.out
Main Start
Created    0x1083008e0
Main Ended
Destroyed  0x1083008e0

スレッド保存期間オブジェクト

これらはこの言語にとって新しいものです。これらは、静的ストレージ期間オブジェクトに非常によく似ています。ただし、アプリケーションの実行スレッドと関連付けられている限り、アプリケーションと同じ生活を送るのではありません。

35
Martin York