1. ホーム
  2. c++

[解決済み】C++のコードを終了させる方法

2022-04-20 08:46:39

質問

C++のコードで、ある条件を満たした場合に実行を停止させたいのですが、その方法がよくわかりません。つまり、どの時点でも、もし if ステートメントが真であれば、このようにコードを終了させます。

if (x==1)
{
    kill code;
}

解決方法は?

いくつかの方法がありますが、まず、なぜオブジェクトのクリーンアップが重要なのかを理解する必要があり、そのために 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 のスタックを調べます。 madmain を含むすべてのオブジェクトが適切に破壊され、適切なクリーンアップが行われるようになります。 ptros .
  • 可能性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]の項参照。 :

  1. 状況によっては、例外処理を放棄して、より巧妙なエラー処理技術を使用しなければならない場合があります。[注:このような状況とは

    [...]

    - 例外処理機構がスローされた例外のハンドラを見つけられない場合 (15.3) や、 ハンドラの検索 (15.3) で関数の一番外側のブロックが noexcept -指定 が例外を許さない場合(15.4)、または[...]です。

    [...]

  2. このような場合、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 [基本.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 をデフォルトで使用します。