C++でオブジェクトが正確に破壊されるのはいつですか?それはどういう意味ですか?ガベージコレクターがないため、手動で破棄する必要がありますか?例外はどのように作用しますか?
(注:これは、 Stack OverflowのC++ FAQ へのエントリとなることを意味します。FAQこのフォームでは、 このすべてを開始したメタの投稿 がそれを行う場所になります。その質問に対する回答は C++チャットルーム 、最初にFAQアイデアが始まったので、あなたの答えはアイデアを思いついた人に読まれそうです。)
次のテキストでは、スコープオブジェクトを区別します。破壊の時間は、そのスコープ(関数、ブロック、クラス、式)によって静的に決定され、動的オブジェクト 、その正確な破棄時間は一般に実行時までわかりません。
クラスオブジェクトの破壊セマンティクスはデストラクタによって決定されますが、スカラーオブジェクトの破壊は常にノーオペレーションです。具体的には、ポインター変数を破棄すると、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
_を忘れると、リソースリークが発生します。これらはすべて未定義の動作につながるため、次のいずれかを実行しようとしないでください。
delete
、free
またはその他の方法で動的配列を破棄しますN番目の要素のconstructionの間に例外がスローされると、要素n-1から0は降順に破棄され、基になるメモリが解放され、例外が伝播されます。
(一般に、動的配列では_std::vector<Foo>
_よりも_Foo*
_を好むはずです。これにより、正確で堅牢なコードの作成がはるかに簡単になります。)
複数の_std::shared_ptr<Foo>
_オブジェクトによって管理される動的オブジェクトは、その動的オブジェクトの共有に関係する最後の_std::shared_ptr<Foo>
_オブジェクトの破棄中に破棄されます。
(一般に、共有オブジェクトでは_std::shared_ptr<Foo>
_よりも_Foo*
_を好むはずです。これにより、正確で堅牢なコードを簡単に記述できます。)
オブジェクトのデストラクタは、オブジェクトの寿命が終了して破棄されると自動的に呼び出されます。通常、手動で呼び出す必要はありません。
このオブジェクトを例として使用します。
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つ)があり、オブジェクトのタイプはオブジェクトの寿命を定義します。
これらは最も単純で、グローバル変数と同等です。これらのオブジェクトの寿命は(通常)アプリケーションの長さです。これらは(通常)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
メンバー変数の寿命は、それを所有するオブジェクトにバインドされます。所有者の寿命が終了すると、すべてのメンバーの寿命も終了します。したがって、同じルールに従う所有者の寿命を調べる必要があります。
注:メンバーは、作成の逆の順序で常に所有者よりも前に破棄されます。
これらは、式の結果として作成されますが、変数に割り当てられないオブジェクトです。一時変数は、他の自動変数と同様に破棄されます。スコープの終わりが、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
これらはこの言語にとって新しいものです。これらは、静的ストレージ期間オブジェクトに非常によく似ています。ただし、アプリケーションの実行スレッドと関連付けられている限り、アプリケーションと同じ生活を送るのではありません。