77

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

(注: これはStack Overflow の C++ FAQへのエントリであることを意図しています。このフォームで FAQ を提供するという考えを批判したい場合は、すべての始まりとなった meta への投稿がそれを行う場所になります。回答への回答その質問は、FAQ のアイデアが最初に始まったC++ チャットルームで監視されているため、アイデアを思いついた人にあなたの回答が読まれる可能性が非常に高くなります。)

4

2 に答える 2

92

以下のテキストでは、破棄の時間がそれを囲むスコープ (関数、ブロック、クラス、式) によって静的に決定されるスコープ オブジェクトと、通常は実行時まで正確な破棄の時間が分からない動的オブジェクトを区別します。

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

スコープ対象のオブジェクト

自動オブジェクト

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

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

関数の実行中に例外がスローされた場合、以前に構築されたすべての自動オブジェクトは、例外が呼び出し元に伝達される前に破棄されます。このプロセスは、スタックの巻き戻しと呼ばれます。スタックの巻き戻し中に、前述の以前に構築された自動オブジェクトのデストラクタからそれ以上例外が発生することはありません。それ以外の場合、関数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

のサブオブジェクトの 1 つの構築中に例外がスローされた場合、Foo以前に構築されたすべてのサブオブジェクトは、例外が伝播される前に破棄されます。一方Foo、デストラクタは、オブジェクトが完全に構築されていないため、実行されません。Foo

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

配列要素

配列要素は降順で破棄されます。n 番目の要素の構築中に例外がスローされた場合、要素 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 を使用して参照を初期化すると、一時オブジェクトが構築されます。

const int& r = i + j;

動的オブジェクトと配列

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

動的オブジェクト

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

  • delete[](角括弧に注意してください)、freeまたはその他の手段を介して動的オブジェクトを破棄します
  • 動的オブジェクトを複数回破壊する
  • 動的オブジェクトが破棄された後にアクセスする

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

動的配列

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

  • deletefreeまたはその他の手段を介して動的配列を破棄します
  • 動的配列を複数回破棄する
  • 動的配列が破棄された後にアクセスする

n 番目の要素の構築中に例外がスローされた場合、n-1 から 0 までの要素が降順に破棄され、基になるメモリが解放され、例外が伝播されます。

(通常、動的配列よりも優先する必要がありstd::vector<Foo>ます。これにより、正確で堅牢なコードをより簡単に記述できます。)Foo*

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

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

(通常、共有オブジェクトよりも優先する必要がありstd::shared_ptr<Foo>ます。これにより、正しく堅牢なコードをより簡単に記述できます。)Foo*

于 2011-06-19T14:43:29.753 に答える
37

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

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

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 の順序で作成さ
    れ、逆の順序で top-->0 で破棄されます。

一時変数

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

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

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

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

于 2012-07-29T19:58:04.570 に答える