サーチ…
例外のキャッチ
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_error
とstd::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;
引数なしでは、現在キャッチされている例外を再スローします。
管理されたstd::exception_ptr
にrethrow_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_error
、 runtime_error
またはそのサブクラスを作成するとbad_alloc
がスローされることがあります。
ネストされた例外
例外処理中に、低レベル関数(ファイルシステムエラーやデータ転送エラーなど)から汎用例外をキャッチして、より高度な例外をスローすると、いくつかの高度な操作が可能であることを示す一般的な使用例があります(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は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()
ます。さらに議題があれば、それを上書きすることができます。