【C++】浅析異常

7584 ワード

異常とは、その名の通り異常で、問題がある.
人間にとって異常な場合は病気になり、プログラムにも異常な場合はコード「病気」があります.
では、病気がある以上治療しなければならないので、対症的に薬を飲まなければなりません.これで正常に戻ることができます.
こんなに多くの話を廃棄して、やはり私達のC++の“異常”の概念を引き出します.
異常は、関数が自分で処理できないエラーを発見したときに異常を投げ出すことができ、その呼び出し者が直接または間接的にこの問題を処理することを望んでいる.
従来の例外処理方法:
1.プログラムの終了
2.エラーを表す値を返します(malloc、メモリ不足、割り当て失敗、NULLポインタを返すなど、多くのシステム関数がそうです).
3.合法的な値を返して、プログラムを何らかの不法な状態にします(最も穴のお父さんのもの、一部の第三者ライブラリは本当にそうします)
4.「エラー」が発生した場合に使用する関数を呼び出します.
1つ目は許されず,無条件にプログラムを終了したライブラリは,その場で実行できないプログラムには適用できない.
第2のケースでは、一般的に使われていますが、エラーコードを返すのがintなど、呼び出しごとにエラー値をチェックするのは不便で、プログラムの規模を倍にしやすい場合があります(ただし、論理を正確に制御するには、この方法がいいと思います).
第3のケースでは、呼び出し者を誤導しやすく、もし呼び出し者がグローバル変数errnoをチェックしたり、他の方法でエラーをチェックしたりしなかったら、それは災難であり、このような方法は同時的にうまく働かない.
4つ目のケースについては、本人は少ないと思います.また、コールバックのコードは多く現れるべきではありません.
異常を使用すると、エラーと処理を分けてライブラリ関数から異常を投げ出し、呼び出し者がこの異常をキャプチャすると、呼び出し者はプログラム関数ライブラリ呼び出しにエラーが発生したことを知って処理するのではなく、プログラムを終了するかどうかは呼び出し者の手に把握されます.
しかし、エラーの処理は依然として困難なことであり、C++の異常メカニズムはプログラマーにエラーを処理する方法を提供し、プログラマーがより自然にエラーを処理できるようにした.
単純な除算プログラムを書くとします
int Div(int a, int b)
{
    if(b == 0)
       exit(1);//   return 0; ?(   ,  0   10/100 )
    return a/b;
}

int main()
{
    int a = 10;
    int b = 2;  //   b = 0   ?
    cout<<Div(a,b)<<endl;
    return 0;
}

このようなプログラムは,一見問題ないが,プログラムが実行中に除数が0のときに終了し,終了はプログラムが実行を継続しないことを意味する,いわゆる異常である.しかし、このように直接終了するのは少し簡単で乱暴ではないでしょうか.これは一般的に私たちが望んでいる結果ではありません.
C++異常中の3本の斧:try,throw,catch
①. あるプログラムに異常が発生するかどうかをテストします
②. 異常が発生した場合はthrowでその異常を放出します(注:この変数のタイプを放出します)
③. 対応する異常、すなわちマッチングタイプの異常をキャプチャし、的確な処理を行う
対応コード:
float Div(int a, int b)
{
    if(b == 0)
    {
       throw b;//          
    }
    return a/b;
}

int main()
{
    int a = 10;
    int b = 0;
    float result = 0.0f;
    try
    {
       result = Div(a,b);
    } 
    catch(int)
    {
        cout<<"Div error!,   0"<<endl;
    }
    cout<<"result = "<<Div(a,b)<<endl;
    
    return 0;
}

実行結果:
これまで異常が投げ出されなかった場合,プログラムはクラッシュ(強制終了プログラムを除く)し,現在はクラッシュせず,問題点を反映していることが分かった.
実際、プログラムに含まれる異常現象は、自身が処理しない場合、オペレーティングシステムに任せて処理され、オペレーティングシステムは機械全体が正常に稼働していることを管理し、このような異常に遭遇すると、直接切り捨てられ、プログラムが終了するため、クラッシュが発生します.プログラムが異常処理を自分で書いた場合,異常の処理は自分で処理する.すなわち,異常処理メカニズムはオペレーティングシステムが発行する2次機構であり,この2次機構は独自のプログラムで設定された異常に対して処理を行う.
プログラムは戻り値ではありません.次のようにします.
左は正常に戻り、異常が発生して右から戻り、異常が発生した後、throw以降のコードは実行されず、catchを探して目を覚ましてキャプチャします.異常仕様:
class Test
{};
float Div(int a, int b)throw(int,double,short,Test)

つまり、この関数は基本タイプint、double、short、およびカスタムタイプTestのみを投げ出すことができます.
float Div(int a, int b)throw()

これは関数が異常を放出できないことを意味します.
float Div(int a, int b)

この代表は異常を投げ出す可能性があります
また、取得時のタイプマッチングの問題が発生しました.
float Div(int a, int b)
{
    if(b == 0)
    {
        short x = 0;
        throw x;//          
    }
    return a/b;
}

int main()
{
    int a = 10;
    int b = 0;
    float result = 0.0f;
    try
    {
       result = Div(a,b);
    } 
    catch(int)
    {
        cout<<"Div error!(int),   0"<<endl;
    }
    catch(short)
    {
        cout<<"Div error!(short),   0"<<endl;
    }
    
    //      double  char        ?        catch?
    //                  
    
    catch(...) //       int short,       !
    {
        cout<<"Div error!(all),   0"<<endl;
    }
    cout<<"result = "<<Div(a,b)<<endl;
    
    return 0;
}

これは似ていますか?私たちが前に勉強したswitch()です.case:文は?
switch()
{
    case:
    case:
    .
    .
    default:
}

つまり、すべてのcase文に一致してdefaultを実行することはできません.同様に、異常中もそうです.
まとめ:
異常な放出とキャプチャ
  • 異常は、どの処理コードをアクティブにすべきかを決定するオブジェクトを放出することによって引き起こされる.
  • で選択された処理コードは、呼び出しチェーン内のオブジェクトタイプと一致し、異常位置から最も近いものである.
  • 異常を放出するとローカルストレージオブジェクトが解放されるので、放出されたオブジェクトもシステムに返され、throw式は特殊な異常オブジェクトのコピー(匿名オブジェクト)を初期化し、異常オブジェクトはコンパイルによって管理され、異常オブジェクトは対応するcatch処理に渡された後に取り消されます.スタック展開
  •    1. 異常が投げ出されると、現在の関数の実行を一時停止し、対応する一致catch句の検索を開始します.
       2. まずthrow自体がcatchブロックの内部にあるかどうかを確認し、一致するcatch文を再検索します.
       3. 一致するものがあれば処理します.ない場合は、現在の関数スタックを終了し、関数を呼び出すスタックで検索を続行します.
       4. 上記の手順を繰り返します.main関数のスタックに到着しても一致しない場合は、プログラムを終了します.
       5. 上記の呼び出しチェーンに沿って一致するcatch句を検索するプロセスをスタック展開と呼ぶ.
    一致するcatch句を見つけて処理すると、catch句の後ろに沿って実行されます.
    以上,異常処理メカニズムの原理を理解した.
    大規模なプログラムコードでは、カスタムタイプの例外を処理する必要があります.
    次に、カスタムタイプマッチングを示します.
    #include <iostream>
    #include <string>
    using namespace std;
    
    class Exception
    {
    public :
         Exception(int errId, const char * errMsg)
             : _errId(errId )
             , _errMsg(errMsg )
        {}
    
         void What () const
        {
              cout<<"errId:" <<_errId<< endl;
              cout<<"errMsg:" <<_errMsg<< endl;
        }
    private :
         int _errId ;       //    
         string _errMsg ;  //     
    };
    
    void Func1 (bool isThrow)
    {
         // ...
         if (isThrow )
        {
              throw Exception (1, "   Excepton  " );
        }
         // ...
    
         printf("Func1(%d)
    " , isThrow); } void Func2 (bool isThrowString, bool isThrowInt) {      // ...      if (isThrowString )     {           throw string ("  string " );     }      // ...      if(isThrowInt )     {           throw 7;     }      printf("Func2(%d, %d)
    " , isThrowString, isThrowInt ); } void Func () {      try     {           Func1(false );           Func2(true , true);     }      catch(const string& errMsg)     {           cout<<"Catch string Object:" <<errMsg<< endl;     }      catch(int errId)     {           cout<<"Catch int Object:" <<errId<< endl;     }      catch(const Exception& e)     {           e.What ();     }      catch(...)     {           cout<<"  "<< endl;     }       printf ("Func()
    "); } int main() {     Func();     return 0; }

    異常な再放出
    単一のcatchでは1つの異常を完全に処理できない可能性があり、いくつかの補正処理を行った後、より外層の呼び出しチェーン関数に渡して処理したい場合、catchは異常をより上層の関数に再放出することで処理することができる.
    class Exception
    {
    public :
         Exception(int errId = 0, const char * errMsg = "" )
             : _errId(errId )
             , _errMsg(errMsg )
        {}
    
         void What () const
        {
              cout<<"errId:" <<_errId<< endl;
              cout<<"errMsg:" <<_errMsg<< endl;
        }
    private :
         int _errId ;       //    
         string _errMsg ;  //     
    };
    
    void Func1 ()
    {
         throw string ("Throw Func1 string");
    }
    
    void Func2 ()
    {
         try
        {
              Func1();
        }
         catch(string & errMsg)
        {
              cout<<errMsg <<endl;
              //Exception e (1, "Rethorw Exception");
              //throw e ;
              // throw;
              // throw errMsg;
        }
    }
    
    void Func3 ()
    {
         try
        {
              Func2();
        }
         catch (Exception & e)
        {
              e.What ();
        }
    }

    異常と構造関数&構造関数
  • コンストラクタは、オブジェクトの構造と初期化を完了します.コンストラクタに異常を投げ出さないようにする必要があります.そうしないと、オブジェクトが不完全になったり、完全に初期化されていない可能性があります.
  • 構造関数は主に資源の整理を完了し、構造関数内に異常を投げ出さないことを保証する必要がある.そうしないと、資源の漏れ(メモリの漏れ、ハンドルの閉じていないなど)
  • を引き起こす可能性がある.
    ExceptionクラスはC++定義の標準的な例外のクラスであり、通常、exceptionクラスを継承することによって適切な例外クラスを定義します.
    http://www.cplusplus.com/reference/exception/exception/
    本文はただ简単に异常な使用シーンから、基本的な使用方法を绍介して、いくつかの高级な异常な使い方は并んでいないで、まだ补充しなければならなくて、偏文は漏れがあるかもしれなくて、みんなに指摘してもらいたいです
    本文は“Vs呂小布”のブログから出て、転載して作者と連絡してください!