C++におけるオブジェクトの破壊
質問
C++でオブジェクトが破棄されるのは正確にはいつですか、そしてそれはどういう意味ですか。ガーベッジコレクタがないので、手動で破棄しなければならないのでしょうか。例外はどのように発生するのでしょうか。
(注意: これは Stack Overflow の C++ FAQ . もし、このような形でFAQを提供するという考えを批判したいのであれば このすべての始まりとなった meta への投稿 がそれを行う場所でしょう。その質問に対する回答は C++チャットルーム で監視されているので、あなたの回答は、このアイデアを思いついた人たちに読まれる可能性が非常に高いのです)。
どのように解決するのですか?
以下の文章で、私は以下のものを区別します。 スコープ付きオブジェクト と、そのスコープ(関数、ブロック、クラス、式)によって破壊のタイミングが静的に決定される ダイナミックオブジェクト であり、その正確な破壊時刻は一般に実行時までわからない。
クラスオブジェクトの破壊のセマンティクスはデストラクタによって決定されますが、スカラオブジェクトの破壊は常にノー・オペレーションです。具体的には、ポインタ変数を破壊することは ではなく はポインタを破壊しません。
スコープされたオブジェクト
自動オブジェクト
自動オブジェクト (一般にローカル変数と呼ばれるもの) は、制御フローがその定義の範囲から離れると、その定義と逆の順序で破壊されます。
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: これは極めて単純化されたモデルです。静的オブジェクトの初期化の詳細は、実際にはもっと複雑です。
ベースクラスサブオブジェクトとメンバサブオブジェクト
制御フローがオブジェクトのデストラクタ本体から離れると、そのメンバーサブオブジェクト(データメンバーとも呼ばれる)は、その定義の逆順に破壊されます。その後、そのベースクラスのサブオブジェクトはベーススペシファイアリストの逆順に破壊されます。
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
のサブオブジェクトの一つを構築した場合、例外が伝搬する前に、 それ以前に構築された全てのサブオブジェクトが破壊されます。そのため
Foo
のデストラクタは、一方では
ではなく
は実行されません。
Foo
オブジェクトが完全に構築されなかったためです。
デストラクタ本体はデータメンバ自身を破壊する責任はないことに注意してください。デストラクタを書く必要があるのは、データメンバが、オブジェクトの破壊時に解放する必要があるリソース(ファイル、ソケット、データベース接続、ミューテックス、ヒープメモリなど)のハンドルである場合だけです。
配列要素
配列の要素は、降順で破壊されます。もし 構築 の構築中に例外が発生した場合、例外が伝播する前に 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番目の一時的なオブジェクトは1番目のオブジェクトの前に破壊されます。
2回目の加算時に例外が発生した場合、1つ目の一時的なオブジェクトは例外を伝播する前に適切に破壊されます。
ローカル参照が prvalue 式で初期化された場合、一時オブジェクトの寿命はローカル参照のスコープまで拡張されるため、ダングリングリファレンスが発生することがありません。
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here
クラス型でないprvalue式が評価された場合、その結果は 値 であり、一時的なオブジェクトではありません。しかし、一時的なオブジェクトである は は構築されます。
const int& r = i + j;
動的なオブジェクトと配列
次のセクションで を破壊する X は、"まずXを破壊し、次に基礎となるメモリを解放する"を意味します。 同様に X を作成する は、"まず十分なメモリを確保し、そこにXを構築する"という意味です。
動的オブジェクト
で作成されたダイナミック・オブジェクトは
p = new Foo
で生成されたオブジェクトは
delete p
. もし
delete p
を忘れると、リソースリークが発生します。これらはすべて未定義の動作につながるので、決してやってはいけません。
-
によって動的オブジェクトを破壊する。
delete[]
で破壊します (角括弧に注意してください)。free
またはその他の手段 - ダイナミック・オブジェクトを複数回破壊する
- 破壊された後にダイナミック・オブジェクトにアクセスする
の実行中に例外が発生した場合 構築 の構築中に例外が発生した場合、その例外が伝搬される前に基礎となるメモリが解放されます。 (デストラクタは ではなく はメモリ解放の前に実行されません。)
動的配列
で作成された動的配列は
p = new Foo[n]
で作成された動的配列は
delete[] p
(を経由して破棄されます(角括弧に注意)。もし
delete[] p
を忘れると、リソースリークが発生します。これらはすべて未定義の動作につながるので、決してやってはいけません。
-
で動的配列を破壊する。
delete
,free
またはその他の手段 - 動的配列を複数回破壊する
- 動的配列が破壊された後、その配列にアクセスする
の実行中に例外が発生した場合 構築 の構築中に例外が発生した場合、n-1 から 0 までの要素が降順で破壊され、 その下のメモリが解放され、例外が伝搬されます。
(一般的には
std::vector<Foo>
よりも
Foo*
を使うようにしました。これにより、正しく堅牢なコードを書くことがより簡単になります)。
参照カウント型スマートポインタ
複数の
std::shared_ptr<Foo>
オブジェクトが破壊される際に、最後の
std::shared_ptr<Foo>
オブジェクトは、そのダイナミックオブジェクトの共有に関与している最後の
(一般的には
std::shared_ptr<Foo>
以上
Foo*
を共有オブジェクトのために使用します。これによって、正しく堅牢なコードを書くことがより簡単になります)。
関連
-
[解決済み】「corrupted size vs. prev_size」glibc エラーを理解する。
-
[解決済み】Visual C++で "Debug Assertion failed "の原因となる行を見つける。
-
[解決済み] 解決済み] `pthread_create' への未定義の参照 [重複] [重複
-
[解決済み】 while(cin) と while(cin >> num) の違いは何ですか?)
-
[解決済み】Visual Studioのデバッガーエラー。プログラムを開始できません 指定されたファイルが見つかりません
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み] 未定義の動作とシーケンスポイント
-
[解決済み] C++コンテナにおけるイテレータの無効化ルール
-
[解決済み] C++で配列はどのように使うのですか?
-
[解決済み] コンパイル・リンクはどのように行われるのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C++ クラスヘッダが含まれているときに「不明な型」があるのはなぜですか?重複
-
[解決済み】getline()が何らかの入力の後に使用されると動作しない 【重複あり
-
[解決済み】識別子 "string "は未定義?
-
[解決済み】C++エラーです。"配列は中括弧で囲まれたイニシャライザーで初期化する必要がある"
-
[解決済み】抽象クラス型の無効なnew-expression
-
[解決済み】c++でstd::vectorを返すための効率的な方法
-
[解決済み】「Expected '(' for function-style cast or type construction」エラーの意味とは?
-
[解決済み】リンカーエラーです。"リンカ入力ファイルはリンクが行われていないため未使用"、そのファイル内の関数への未定義参照
-
[解決済み】CMakeエラー at CMakeLists.txt:30 (project)。CMAKE_C_COMPILER が見つかりませんでした。
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件