なぜC++標準がわざわざstd::exception
クラスを発明したのですか?彼らの利点は何ですか?私が尋ねる理由はこれです:
try
{
throw std::string("boom");
}
catch (std::string str)
{
std::cout << str << std::endl;
}
正常に動作します。後で、必要に応じて、独自の軽量の「例外」タイプを作成できます。では、なぜ私は気にする必要がありstd::exception
ますか?
なぜC++標準がわざわざ
std::exception
クラスを発明したのですか?彼らの利点は何ですか?
これは、標準ライブラリによってスローされた例外を処理するための汎用的で一貫性のあるインターフェイスを提供します。標準ライブラリによって生成されたすべての例外は、から継承されstd::exception
ます。
標準ライブラリのAPIは、さまざまな種類の例外をスローする可能性があることに注意してください。いくつかの例を引用すると、次のようになります。
std::bad_alloc
std::bad_cast
std::bad_exception
std::bad_typeid
std::logic_error
std::runtime_error
std::bad_weak_ptr | C++11
std::bad_function_call | C++11
std::ios_base::failure | C++11
std::bad_variant_access | C++17
など...
std::exception
これらすべての例外の基本クラスです。
これらすべての例外の基本クラスを提供すると、共通の例外ハンドラーを使用して複数の例外を処理できます。
必要に応じて、独自の軽量の「例外」タイプを作成できます。では、なぜ私は気にする必要がありstd::exception
ますか?
カスタム例外クラスが必要な場合は、先に進んで作成してください。ただしstd::exception
、優れた例外クラスに必要な多くの機能がすでに提供されているため、作業が簡単になります。これにより、クラスの機能に必要な関数(特にstd::exception::what()
)を簡単に導き出すことができます。これにより、ハンドラーに
は2つの利点があります。std::exception
なぜC++標準はstd::exceptionクラスを発明するのをわざわざしたのですか?彼らの利点は何ですか?
さまざまな種類の例外があると、特定の種類のエラーをキャッチできます。共通ベースから例外を派生させることで、状況に応じて、より一般的または特定のエラーをきめ細かくキャッチできます。
C ++では、既存の型システムがすでに導入されているため、言語で目的の型の例外を明示的に作成できる場合は、エラー文字列を標準化する必要はありませんでした。
std :: exceptionとその派生クラスは、主に2つの理由で存在します。
標準ライブラリには、例外的な状況でスローするためのある種の例外階層が必要です。特定の種類のエラーをターゲットにするクリーンな方法がないため、常にstd::stringをスローすることは不適切です。
ライブラリベンダーが最も基本的なエラータイプをスローし、ユーザーに共通のフォールバックを提供するための拡張可能なクラスベースのインターフェイスを提供するため。エラーをキャッチした人がよりインテリジェントにエラーから回復できるように、単純なwhat()文字列よりも多くのエラーメタデータを提供することをお勧めします。
同時に、共通ベースとしてのstd ::例外により、ユーザーがそのエラーメッセージのみを気にする場合、一般的なキャッチオールは...よりも包括的ではありません。
印刷して終了するだけの場合、それは実際には問題ではありませんが、キャッチの便宜のためにstd::exceptionから継承するstd::runtime_errorを使用することもできます。
後で、必要に応じて、独自の軽量の「例外」タイプを作成できます。では、なぜstd :: exceptionを気にする必要があるのでしょうか?
std :: runtime_errorから継承し、独自のカスタムエラータイプを使用する場合は、catchブロックを書き直すことなく、エラーメタデータをさかのぼって追加できます。対照的に、エラー処理の設計を変更した場合は、 std :: stringから安全に継承できないため、すべてのstd::stringキャッチを書き直す必要があります。これは、将来を見据えた設計上の決定ではありません。
それが今のところそれほど悪くないように思われる場合は、コードが複数のプロジェクト間で共有ライブラリとして共有され、さまざまなプログラマーが作業していると想像してみてください。ライブラリの新しいバージョンに移行するのは面倒です。
これは、std :: stringが文字のコピー、構築、またはアクセス中に独自の例外をスローする可能性があることにも言及していません。
BoostのWebサイトには、例外処理とクラス構築に関するいくつかの優れたガイドラインがあります。
私はいくつかのネットワークコードを書いていて、サードパーティベンダーのライブラリを使用しています。ユーザーが無効なIPアドレスを入力すると、このライブラリはstd::runtime_errorから派生し たカスタム例外nw::invalid_ipをスローします。nw :: invalid_ipには、エラーメッセージを説明するwhat()が含まれていますが、指定されたincorrect_ip()アドレスも含まれています。
また、std :: vectorを使用してソケットを格納し、checked at()呼び出しを使用してインデックスに安全にアクセスしています。範囲外の値でat()を呼び出すと、 std::out_of_rangeがスローされることを知っています。
他のものも投げられるかもしれないことは知っていますが、それらをどのように扱うか、正確には何であるかはわかりません。
nw :: invalid_ipエラーが発生した場合、無効なIPアドレスが入力されたユーザーの入力ボックスを含むモーダルをポップアップして、編集して再試行できるようにします。
std :: out_of_rangeの問題については、ソケットで整合性チェックを実行し、同期から外れたベクトル/ソケットの関係を修正することで対応します。
その他のstd::例外の問題については、エラーログでプログラムを終了します。最後に、「不明なエラー」をログに記録するcatch(...)があります。そして終了します。
std :: stringがスローされるだけで、これを確実に実行することは困難です。
これは、例外をキャッチして遊ぶことができるように、さまざまなケースでスローされるいくつかの基本的な例です。
#include <vector>
#include <iostream>
#include <functional>
#include <stdexcept>
#include <bitset>
#include <string>
struct Base1 {
virtual ~Base1(){}
};
struct Base2 {
virtual ~Base2(){}
};
class Class1 : public Base1 {};
class Class2 : public Base2 {};
class CustomException : public std::runtime_error {
public:
explicit CustomException(const std::string& what_arg, int errorCode):
std::runtime_error(what_arg),
errorCode(errorCode){
}
int whatErrorCode() const {
return errorCode;
}
private:
int errorCode;
};
void tryWrap(typename std::function<void()> f){
try {
f();
} catch(CustomException &e) {
std::cout << "Custom Exception: " << e.what() << " Error Code: " << e.whatErrorCode() << std::endl;
} catch(std::out_of_range &e) {
std::cout << "Range exception: " << e.what() << std::endl;
} catch(std::bad_cast &e) {
std::cout << "Cast exception: " << e.what() << std::endl;
} catch(std::exception &e) {
std::cout << "General exception: " << e.what() << std::endl;
} catch(...) {
std::cout << "What just happened?" << std::endl;
}
}
int main(){
Class1 a;
Class2 b;
std::vector<Class2> values;
tryWrap([](){
throw CustomException("My exception with an additional error code!", 42);
});
tryWrap([&](){
values.at(10);
});
tryWrap([&](){
Class2 c = dynamic_cast<Class2&>(a);
});
tryWrap([&](){
values.push_back(dynamic_cast<Class2&>(a));
values.at(1);
});
tryWrap([](){
std::bitset<5> mybitset (std::string("01234"));
});
tryWrap([](){
throw 5;
});
}
Custom Exception: My exception with an additional error code! Error Code: 42
Range exception: vector::_M_range_check
Cast exception: std::bad_cast
Cast exception: std::bad_cast
General exception: bitset::_M_copy_from_ptr
What just happened?
std::exception
実際には単一のプロパティのみが含まれているため、これは正当な質問です: what()
、string
。string
したがって、の代わりに使用するのは魅力的ですexception
。しかし、実際には、はでexception
はありませんstring
。例外を単なるのように扱うstring
と、より多くのプロパティを提供する特殊な例外クラスで例外を派生させることができなくなります。
たとえば、今日string
、あなたはあなた自身のコードにsを投げます。明日、データベース接続の例外など、特定のケースにさらにプロパティを追加することにしました。string
この変更を行うために派生することはできません。新しい例外クラスを作成し、のすべての例外ハンドラーを変更する必要がありますstring
。使用exception
は、例外ハンドラーが関心のあるデータのみを使用し、処理する必要があるときに例外を選択するための方法です。
また、型指定された例外のみをスローして処理するstring
と、自分のものではないコードからスローされたすべての例外を見逃してしまいます。この区別が意図的なものである場合は、一般的なタイプの。ではなく、一般的な例外クラスを使用してこれを示すのが最適ですstring
。
exception
また、より具体的ですstring
。これは、ライブラリ開発者が例外をパラメータとして受け入れる関数を記述できることを意味します。これは、を受け入れるよりも明確ですstring
。
これらはすべて基本的に無料で、のexception
代わりに使用してstring
ください。
6行のおもちゃの例で何かが「正常に機能する」からといって、それが実際のコードでスケーラブルまたは保守可能であることを意味するわけではありません。
この関数について考えてみましょう。
template<typename T>
std::string convert(const T& t)
{
return boost:lexical_cast<std::string>(t);
}
これはbad_alloc
、文字列のメモリを割り当てることができない場合にスローされる可能性が bad_cast
あり、変換が失敗した場合にスローされる可能性があります。
この関数の呼び出し元は、入力が正しくないことを示すが致命的なエラーではない変換の失敗のケースを処理したい場合がありますが、メモリ不足のケースは処理できないため、処理できません。例外はスタックを伝播します。これは、C++で非常に簡単に実行できます。次に例を示します。
std::string s;
try {
s = convert(val);
} catch (const std::bad_cast& e) {
s = "failed";
}
std::string
コードが次のようになっているため、例外がスローされた場合:
std::string s;
try {
s = convert(val);
} catch (const std::string& e) {
if (e.find("bad_cast") != std::string::npos)
s = "failed";
else
throw;
}
これは、実装するためにより多くのコードを必要とし、例外文字列の正確な表現に依存します。これは、コンパイラの実装との定義に依存する可能性がありますboost::lexical_cast
。システム内のすべての例外処理で文字列比較を実行して、その時点でエラーを処理できるかどうかを判断する必要がある場合、エラーは厄介で保守不可能になります。例外をスローするシステムの一部で例外メッセージのスペルを少し変更すると、システムの別の部分で例外処理コードが機能しなくなる可能性があります。これにより、エラーの場所とすべてのシステムのエラー処理コードのビット。例外を使用する利点の1つは、エラー処理をメインロジックから分離できることです。システム全体の文字列比較に基づいて依存関係を作成すると、その利点が失われます。
C ++での例外処理は、例外の型を照合することによってキャッチしますが、例外の値を照合することによってキャッチすることはありません。したがって、さまざまな型のものをスローして、きめ細かい処理を可能にすることは理にかなっています。単一の文字列型のものをスローし、文字列の値に基づいてそれらを処理することは、面倒で、移植性がなく、より困難です。
後で、必要に応じて、独自の軽量の「例外」タイプを作成できます。では、なぜstd :: exceptionを気にする必要があるのでしょうか?
コードが有用で再利用可能であり、システムの一部で使用したい場合、すべての軽量タイプをキャッチする例外処理を追加する必要がありますか?システムの1ビットが依存しているライブラリの内部の詳細をシステム全体が気にする必要があるのはなぜですか?カスタム例外タイプが派生している場合、特定のタイプについて知らなくても(または気にせずに)std::exception
それらをキャッチできます。const std::exception&
クラスの唯一のユーザーである場合は、回避できますstd::exception
(標準ライブラリの例外を回避したい場合) 。
しかし、あなたのクラスが他の人(プログラマー)によって使用される場合、彼らは例外をどのように処理しますか?
エラーを説明するクラスthrows
aの場合、クラスのコンシューマーは文字列をキャッチするのではなく、より標準的な方法(例外オブジェクトをキャッチし、それがどのメソッドであるかをクエリする)を好むため、役に立ちません。string
exception
exception
また、オブジェクトをキャッチすることで、標準ライブラリによってスローされた例外をキャッチできます。
例外what
クラスのメソッドをオーバーライドして、エラーに関する詳細情報を提供できます。
うわー、私は誰もこれに言及していないことに驚いています:
例外を区別できるようにするには、複数の種類の例外が必要です。ある種の例外は処理する必要がありますが、他の種類の例外は処理しないでください。
プログラムがスローする可能性のあるすべてのタイプの例外を知らなくても、ユーザーに適切なメッセージを表示する手段を提供できるように、共通の基本クラスが必要です(外部のクローズドソースライブラリを使用する場合は不可能です)。