サーチ…


例外のキャッチ

try/catchブロックは、例外を捕捉するために使用されます。 tryセクションのコードは例外をスローするコードで、 catch句のコードが例外を処理します。

#include <iostream>
#include <string>
#include <stdexcept>

int main() {
  std::string str("foo");
  
  try {
      str.at(10); // access element, may throw std::out_of_range
  } catch (const std::out_of_range& e) {
      // what() is inherited from std::exception and contains an explanatory message
      std::cout << e.what();
  }
}

複数の例外タイプを処理するには、複数のcatch節を使用できます。複数のcatch句が存在する場合、例外処理メカニズムはコード内に出現する順にそれらを一致させようとします。

std::string str("foo");
  
try {
    str.reserve(2); // reserve extra capacity, may throw std::length_error
    str.at(10); // access element, may throw std::out_of_range
} catch (const std::length_error& e) {
    std::cout << e.what();
} catch (const std::out_of_range& e) {
    std::cout << e.what();
}

共通基本クラスから派生した例外クラスは、共通基本クラスに対して単一のcatch節でcatchできます。上記の例では、 std::length_errorstd::out_of_rangeの2つのcatch節をstd::length_errorの1つの節に置き換えることができstd:exception

std::string str("foo");
  
try {
    str.reserve(2); // reserve extra capacity, may throw std::length_error
    str.at(10); // access element, may throw std::out_of_range
} catch (const std::exception& e) {
    std::cout << e.what();
}

catch節は順番に試されるので、より具体的なcatch節を最初に記述してください。さもなければ、例外処理コードは決して呼び出されないかもしれません:

try {
    /* Code throwing exceptions omitted. */
} catch (const std::exception& e) {
    /* Handle all exceptions of type std::exception. */
} catch (const std::runtime_error& e) {
    /* This block of code will never execute, because std::runtime_error inherits
       from std::exception, and all exceptions of type std::exception were already
       caught by the previous catch clause. */
}

もう1つの可能性は、スローされたオブジェクトをキャッチするcatch-allハンドラです。

try {
    throw 10;
} catch (...) {
    std::cout << "caught an exception";
}

例外を再スローする(伝播する)

場合によっては、キャッチした例外(ログの書き込みや警告の印刷など)で何かを行い、処理対象の上限にバブルアップさせたい場合もあります。これを行うには、あなたが捕まえる例外をすべて投げ直すことができます:

try {
    ... // some code here
} catch (const SomeException& e) {
    std::cout << "caught an exception";
    throw;
}

throw;を使うthrow;引数なしでは、現在キャッチされている例外を再スローします。

C ++ 11

管理されたstd::exception_ptrrethrow_exceptionために、C ++標準ライブラリにrethrow_exception関数があります。 rethrow_exception関数は、プログラムに<exception>ヘッダを含めることによって使用できます。

#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
 
void handle_eptr(std::exception_ptr eptr) // passing by value is ok
{
    try {
        if (eptr) {
            std::rethrow_exception(eptr);
        }
    } catch(const std::exception& e) {
        std::cout << "Caught exception \"" << e.what() << "\"\n";
    }
}
 
int main()
{
    std::exception_ptr eptr;
    try {
        std::string().at(1); // this generates an std::out_of_range
    } catch(...) {
        eptr = std::current_exception(); // capture
    }
    handle_eptr(eptr);
} // destructor for std::out_of_range called here, when the eptr is destructed

機能ブロックを試すコンストラクタ

イニシャライザのリストで例外をキャッチする唯一の方法:

struct A : public B
{
    A() try : B(), foo(1), bar(2)
    {
        // constructor body 
    }
    catch (...)
    {
        // exceptions from the initializer list and constructor are caught here
        // if no exception is thrown here
        // then the caught exception is re-thrown.
    }
 
private:
    Foo foo;
    Bar bar;
};

機能通常の機能のためにブロックを試す

void function_with_try_block() 
try
{
    // try block body
} 
catch (...) 
{ 
    // catch block body
}

どちらと同じです

void function_with_try_block() 
{
    try
    {
        // try block body
    } 
    catch (...) 
    { 
        // catch block body
    }
}

コンストラクタとデストラクタでは、catchブロックが例外をもう一度スローします(キャッチブロック本体に他のスローがない場合はキャッチされたもの)ので、動作は異なります。

関数mainは、他の関数と同様にtryブロックを持つことができますが、 mainの関数tryブロックは、非ローカル静的変数の構築中または静的変数の破棄中に発生する例外をキャッチしません。代わりに、 std::terminateが呼び出されます。

機能デストラクタでブロックを試す

struct A
{
    ~A() noexcept(false) try
    {
        // destructor body 
    }
    catch (...)
    {
        // exceptions of destructor body are caught here
        // if no exception is thrown here
        // then the caught exception is re-thrown.
    }
};

これは可能ですが、デストラクタからの投げには注意が必要です。スタックの巻き戻し中に呼び出されたデストラクタが例外をスローしたように、 std::terminateが呼び出されます。

ベストプラクティス:値渡し、const参照のキャッチ

一般的に、(ポインタではなく)値でスローするのは良い習慣とみなされますが、(const)参照ではキャッチされません。

try {
    // throw new std::runtime_error("Error!");   // Don't do this!
    // This creates an exception object
    // on the heap and would require you to catch the
    // pointer and manage the memory yourself. This can
    // cause memory leaks!
    
    throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
    std::cout << e.what() << std::endl;
}

参照によってキャッチするのが良い習慣である理由の1つは、キャッチブロックに渡されるとき(または他のキャッチブロックに伝播するとき)にオブジェクトを再構築する必要性を排除することです。また、参照によってキャッチすることで、例外を多態的に扱うことができ、オブジェクトのスライスを避けることができます。しかし、例外を再throw e;ている場合( throw e;ように、以下の例を参照してください)、オブジェクトスライシングを得ることができますthrow e;文は宣言されている型として例外のコピーを作成します:

#include <iostream>

struct BaseException {
    virtual const char* what() const { return "BaseException"; }
};

struct DerivedException : BaseException {
    // "virtual" keyword is optional here
    virtual const char* what() const { return "DerivedException"; }
};

int main(int argc, char** argv) {
    try {
        try {
            throw DerivedException();
        } catch (const BaseException& e) {
            std::cout << "First catch block: " << e.what() << std::endl;
            // Output ==> First catch block: DerivedException

            throw e; // This changes the exception to BaseException
                     // instead of the original DerivedException!
        }
    } catch (const BaseException& e) {
        std::cout << "Second catch block: " << e.what() << std::endl;
        // Output ==> Second catch block: BaseException
    }
    return 0;
}

例外の変更(情報の追加やメッセージの変更など)を行わないことが確実な場合は、const参照をキャッチするとコンパイラは最適化を行い、パフォーマンスを向上させることができます。しかし、これは依然としてオブジェクトスプライシングを引き起こす可能性があります(上記の例を参照)。

警告:特にメモリやリソースを余分に割り当てることに関連して、意図しない例外をcatchブロックに投げることに注意してください。たとえば、例外文字列のコピー時にメモリが不足したり、例外マスクが設定されたロギング中にI / Oストリームがスローされたりする可能性があるため、 logic_errorruntime_errorまたはそのサブクラスを作成するとbad_allocがスローされることがあります。

ネストされた例外

C ++ 11

例外処理中に、低レベル関数(ファイルシステムエラーやデータ転送エラーなど)から汎用例外をキャッチして、より高度な例外をスローすると、いくつかの高度な操作が可能であることを示す一般的な使用例があります(Web上に写真を公開することができないなど)実行されません。これにより、例外処理が高水準の操作で特定の問題に反応することが可能になります。また、エラーだけでエラーが発生すると、プログラマーは例外が発生したアプリケーション内の場所を見つけることができます。このソリューションの欠点は、例外コールスタックが切り捨てられ、元の例外が失われることです。これにより、開発者はオリジナルの例外のテキストを新しく作成したテキストに手動で含めることができます。

ネストされた例外は、原因を説明する低レベルの例外を高レベルの例外に結びつけることによって問題を解決することを目的としています。

std::nested_exception巣の例外にすることができます感謝std::throw_with_nested

#include <stdexcept>
#include <exception>
#include <string>
#include <fstream>
#include <iostream>

struct MyException
{
    MyException(const std::string& message) : message(message) {}
    std::string message;
};

void print_current_exception(int level)
{
    try {
        throw;
    } catch (const std::exception& e) {
        std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
    } catch (const MyException& e) {
        std::cerr << std::string(level, ' ') << "MyException: " << e.message << '\n';
    } catch (...) {
        std::cerr << "Unkown exception\n";
    }
}

void print_current_exception_with_nested(int level =  0)
{
    try {
        throw;
    } catch (...) {
        print_current_exception(level);
    }    
    try {
        throw;
    } catch (const std::nested_exception& nested) {
        try {
            nested.rethrow_nested();
        } catch (...) {
            print_current_exception_with_nested(level + 1); // recursion
        }
    } catch (...) {
        //Empty // End recursion
    }
}

// sample function that catches an exception and wraps it in a nested exception
void open_file(const std::string& s)
{
    try {
        std::ifstream file(s);
        file.exceptions(std::ios_base::failbit);
    } catch(...) {
        std::throw_with_nested(MyException{"Couldn't open " + s});
    }
}
 
// sample function that catches an exception and wraps it in a nested exception
void run()
{
    try {
        open_file("nonexistent.file");
    } catch(...) {
        std::throw_with_nested( std::runtime_error("run() failed") );
    }
}
 
// runs the sample function above and prints the caught exception
int main()
{
    try {
        run();
    } catch(...) {
        print_current_exception_with_nested();
    }
}

可能な出力:

exception: run() failed
 MyException: Couldn't open nonexistent.file
  exception: basic_ios::clear

std::exceptionから継承した例外でのみ作業する場合は、コードを単純化することもできます。

std :: uncaught_exceptions

c ++ 17

C ++ 17はint std::uncaught_exceptions()導入しました(制限されたbool std::uncaught_exception()を置き換えて)、現在捕捉されていない例外の数を知りました。これにより、クラスがスタックの巻き戻し中に破棄されるかどうかを判断することができます。

#include <exception>
#include <string>
#include <iostream>

// Apply change on destruction:
// Rollback in case of exception (failure)
// Else Commit (success)
class Transaction
{
public:
    Transaction(const std::string& s) : message(s) {}
    Transaction(const Transaction&) = delete;
    Transaction& operator =(const Transaction&) = delete;
    void Commit() { std::cout << message << ": Commit\n"; }
    void RollBack() noexcept(true) { std::cout << message << ": Rollback\n"; }

    // ...

    ~Transaction() {
        if (uncaughtExceptionCount == std::uncaught_exceptions()) {
            Commit(); // May throw.
        } else { // current stack unwinding
            RollBack();
        }
    }

private:
    std::string message;
    int uncaughtExceptionCount = std::uncaught_exceptions();
};

class Foo
{
public:
    ~Foo() {
        try {
            Transaction transaction("In ~Foo"); // Commit,
                                            // even if there is an uncaught exception
            //...
        } catch (const std::exception& e) {
            std::cerr << "exception/~Foo:" << e.what() << std::endl;
        }
    }
};

int main()
{
    try {
        Transaction transaction("In main"); // RollBack
        Foo foo; // ~Foo commit its transaction.
        //...
        throw std::runtime_error("Error");
    } catch (const std::exception& e) {
        std::cerr << "exception/main:" << e.what() << std::endl;
    }
}

出力:

In ~Foo: Commit
In main: Rollback
exception/main:Error

カスタム例外

生の値を例外として投げてはならず、代わりに標準の例外クラスを使用するか、独自の例外クラスを作成してください。

std::exceptionから継承した独自の例外クラスを持つことは、それを行う良い方法です。以下はstd::exceptionから直接継承したカスタム例外クラスstd::exception

#include <exception>

class Except: virtual public std::exception {
    
protected:

    int error_number;               ///< Error number
    int error_offset;               ///< Error offset
    std::string error_message;      ///< Error message
    
public:

    /** Constructor (C++ STL string, int, int).
     *  @param msg The error message
     *  @param err_num Error number
     *  @param err_off Error offset
     */
    explicit 
    Except(const std::string& msg, int err_num, int err_off):
        error_number(err_num),
        error_offset(err_off),
        error_message(msg)
        {}

    /** Destructor.
     *  Virtual to allow for subclassing.
     */
    virtual ~Except() throw () {}

    /** Returns a pointer to the (constant) error description.
     *  @return A pointer to a const char*. The underlying memory
     *  is in possession of the Except object. Callers must
     *  not attempt to free the memory.
     */
    virtual const char* what() const throw () {
       return error_message.c_str();
    }
    
    /** Returns error number.
     *  @return #error_number
     */
    virtual int getErrorNumber() const throw() {
        return error_number;
    }
    
    /**Returns error offset.
     * @return #error_offset
     */
    virtual int getErrorOffset() const throw() {
        return error_offset;
    }

};

スローキャッチの例:

try {
    throw(Except("Couldn't do what you were expecting", -12, -34));
} catch (const Except& e) {
    std::cout<<e.what()
             <<"\nError number: "<<e.getErrorNumber()
             <<"\nError offset: "<<e.getErrorOffset();
}

ダムのエラーメッセージを投げるだけでなく、エラーの内容を正確に表す他の値を使用するだけでなく、エラー処理がはるかに効率的かつ有意義になります。

エラーメッセージをうまく処理できるようにする例外クラスがあります: std::runtime_error

あなたはこのクラスから継承することもできます:

#include <stdexcept>

class Except: virtual public std::runtime_error {
    
protected:

    int error_number;               ///< Error number
    int error_offset;               ///< Error offset
    
public:

    /** Constructor (C++ STL string, int, int).
     *  @param msg The error message
     *  @param err_num Error number
     *  @param err_off Error offset
     */
    explicit 
    Except(const std::string& msg, int err_num, int err_off):
        std::runtime_error(msg)
        {
            error_number = err_num;
            error_offset = err_off;
            
        }

    /** Destructor.
     *  Virtual to allow for subclassing.
     */
    virtual ~Except() throw () {}
    
    /** Returns error number.
     *  @return #error_number
     */
    virtual int getErrorNumber() const throw() {
        return error_number;
    }
    
    /**Returns error offset.
     * @return #error_offset
     */
    virtual int getErrorOffset() const throw() {
        return error_offset;
    }

};

私はwhat()関数を基本クラス( std::runtime_error )からオーバーライドしていないことに注意してください。つまり、基本クラスのwhat()ます。さらに議題があれば、それを上書きすることができます。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow