いくつかの方法がありますが、最初にオブジェクトのクリーンアップが重要である理由を理解する必要がありstd::exit
ます。そのため、C ++プログラマーの間ではその理由は疎外されます。
RAIIとスタックの巻き戻し
C ++はRAIIと呼ばれるイディオムを使用します。これは、簡単に言えば、オブジェクトはコンストラクターで初期化を実行し、デストラクターでクリーンアップを実行する必要があることを意味します。たとえば、std::ofstream
クラスはコンストラクターの間にファイルを開く可能性があり、ユーザーはそれに対して出力操作を実行し、最後に通常はそのスコープによって決定されるそのライフサイクルの最後に、デストラクターが呼び出されて本質的にファイルを閉じてフラッシュしますディスクに書き込まれたコンテンツ。
デストラクタがファイルをフラッシュして閉じない場合はどうなりますか?知るか!ただし、ファイルに書き込むはずのデータがすべて書き込まれるとは限りません。
たとえば、このコードを検討してください
#include <fstream>
#include <exception>
#include <memory>
void inner_mad()
{
throw std::exception();
}
void mad()
{
auto ptr = std::make_unique<int>();
inner_mad();
}
int main()
{
std::ofstream os("file.txt");
os << "Content!!!";
int possibility = /* either 1, 2, 3 or 4 */;
if(possibility == 1)
return 0;
else if(possibility == 2)
throw std::exception();
else if(possibility == 3)
mad();
else if(possibility == 4)
exit(0);
}
それぞれの可能性で何が起こるかです:
- 可能性1: Returnは基本的に現在の関数スコープを離れるので、ライフサイクルの終わりを知っているため、
os
そのデストラクタを呼び出し、ファイルを閉じてディスクにフラッシュすることによって適切なクリーンアップを実行します。
- 可能性2:例外をスローすると、現在のスコープ内のオブジェクトのライフサイクルも処理され、適切なクリーンアップが行われます...
- 可能性3:スタックの巻き戻しが実行されます!例外がスローでされていても
inner_mad
、アンワインダーはスタックものの行くmad
とmain
適切なクリーンアップを実行するために、すべてのオブジェクトには、適切に破壊しようとしているptr
とos
。
- 可能性4:まあ、ここ?
exit
C関数であり、C ++イディオムを認識しておらず、互換性もありません。非常に同じスコープを含む、オブジェクトのクリーンアップは実行されませんos
。したがって、ファイルが適切に閉じられないため、コンテンツがファイルに書き込まれない可能性があります。
- その他の可能性:暗黙的に実行し、
return 0
可能性1と同じ効果、つまり適切なクリーンアップを行うことで、メインスコープを離れます。
しかし、私があなたに今言った内容(主に可能性2と3)についてはそれほど確信してはいけません。読み続けて、適切な例外ベースのクリーンアップを実行する方法を見つけます。
終了する可能な方法
メインから戻ります!
可能な限りこれを行う必要があります。mainから適切な終了ステータスを返すことにより、常にプログラムから戻ることを好みます。
プログラムの呼び出し元、および場合によってはオペレーティングシステムは、プログラムが実行するはずの処理が正常に行われたかどうかを知りたい場合があります。これと同じ理由で、ゼロを返すかEXIT_SUCCESS
、プログラムが正常に終了したことを通知し、プログラムが正常に終了しEXIT_FAILURE
なかったことを通知する必要があります。他の形式の戻り値は実装定義です(§18.5/ 8)。
ただし、コールスタックが非常に深く、すべてを返すのは面倒な場合があります...
例外を投げる[しない]
例外をスローすると、以前のスコープ内のすべてのオブジェクトのデストラクタを呼び出すことにより、スタックの巻き戻しを使用して適切なオブジェクトのクリーンアップを実行します。
しかし、ここに問題があります!スローされた例外が(catch(...)句によって)処理されない場合、またはnoexcept
呼び出しスタックの途中に関数がある場合でも、スタックの巻き戻しが実行されるかどうかは、実装によって定義されます。これは§15.5.1[except.terminate]で述べられています:
状況によっては、エラー処理技術の微妙さを減らすために、例外処理を中止する必要があります。[注:これらの状況は次のとおりです。
[...]
- ときのメカニズムを扱う例外がスローされた例外(15.3)のハンドラを見つけることができない場合、またはハンドラ(15.3)の検索が持つ機能の最も外側のブロックに遭遇noexcept
-specification例外を許可しない(15.4)、または[...]
[...]
そのような場合、std :: terminate()が呼び出されます(18.8.3)。一致するハンドラーが見つからない場合、std :: terminate()が呼び出される前にスタックが巻き戻されるかどうかは実装定義です[...]
だから私たちはそれをキャッチする必要があります!
例外をスローしてメインでキャッチしてください!
キャッチされない例外はスタックの巻き戻しを実行しない可能性があるため(したがって、適切なクリーンアップは実行されません)、メインで例外をキャッチしてから、終了ステータス(EXIT_SUCCESS
またはEXIT_FAILURE
)を返す必要があります。
したがって、おそらく良い設定は次のようになります。
int main()
{
/* ... */
try
{
// Insert code that will return by throwing a exception.
}
catch(const std::exception&) // Consider using a custom exception type for intentional
{ // throws. A good idea might be a `return_exception`.
return EXIT_FAILURE;
}
/* ... */
}
std :: exit [するな]
これは、いかなる種類のスタックの巻き戻しも実行せず、スタック上の生きているオブジェクトは、それぞれのデストラクタを呼び出してクリーンアップを実行しません。
これは、§3.6.1/ 4 [basic.start.init]で実施されています。
現在のブロックを離れずにプログラムを終了しても(たとえば、関数std :: exit(int)(18.5)を呼び出して)、自動保存期間(12.4)のオブジェクトは破棄されません。静的またはスレッドストレージ期間のオブジェクトの破棄中にstd :: exitを呼び出してプログラムを終了すると、プログラムの動作は未定義になります。
今考えてみてください、なぜそんなことをするのですか?痛いほど多くのオブジェクトを損傷しましたか?
その他の[悪い]代替案
プログラムを終了する方法は他にもあります(クラッシュ以外)が、お勧めできません。明確にするために、ここではそれらを紹介します。お知らせどのように通常のプログラムの終了が ない平均スタックの巻き戻しが、大丈夫オペレーティングシステムの状態を。
main()
使用リターン、機能に適切な戻り値を使用するか、適切な例外をスローします。使用しないでくださいexit()
!