特定の条件が満たされた場合に C++ コードの実行を停止したいのですが、その方法がわかりません。したがって、ステートメントif
が true の場合は、次のようにコードを終了します。
if (x==1)
{
kill code;
}
ベストアンサー1
いくつかの方法がありますが、まずオブジェクトのクリーンアップがなぜ重要なのか、そしてその理由を理解する必要があります。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 から適切な終了ステータスを返すことによってプログラムから戻るようにしてください。
プログラムの呼び出し元、そしておそらくオペレーティングシステムは、プログラムが実行しようとしていたことが正常に行われたかどうかを知りたいと思うかもしれません。同じ理由で、0または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() が呼び出される前にスタックがアンワインドされるかどうかは実装定義です[...]
だから捕まえなきゃ!
例外をスローして、メインでキャッチしてください。
キャッチされない例外はスタックの巻き戻しを実行しない可能性があるため(したがって適切なクリーンアップを実行しない)、mainで例外をキャッチして終了ステータスを返す必要があります(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 を呼び出してプログラムを終了した場合、プログラムの動作は未定義になります。
今考えてみてください。なぜそんなことをするのでしょうか? これまでにどれだけの物を痛ましく傷つけてきたでしょうか?
他の[同様に悪い]代替案
プログラムを終了する方法は他にもありますが (クラッシュ以外)、お勧めできません。ここでは説明のためにそれらを紹介します。通常のプログラム終了はスタックのアンワインドでは なく、オペレーティング システムにとって正常な状態を意味することに注意してください。
std::_Exit
通常のプログラム終了を引き起こし、それだけです。std::quick_exit
通常のプログラム終了を引き起こし、std::at_quick_exit
ハンドラでは、その他のクリーンアップは実行されません。std::exit
通常のプログラムを終了させ、その後std::atexit
ハンドラ。静的オブジェクトのデストラクタの呼び出しなど、その他の種類のクリーンアップも実行されます。std::abort
異常なプログラム終了を引き起こした場合、クリーンアップは実行されません。プログラムが本当に予期しない方法で終了した場合にこれを呼び出す必要があります。異常終了について OS に通知する以外は何も行いません。一部のシステムでは、この場合にコア ダンプを実行します。std::terminate
呼び出しstd::terminate_handler
呼び出しstd::abort
デフォルトでは。