std::error_code, std::error_category, std::error_conditionの違い
概要
C++11で <system_error> ヘッダが追加され、その中に3つのクラス
std::error_codestd::error_categorystd::error_condition
が追加されました。自分はこの3つクラスの違いがわからなかったので調べてまとめてみました。ソースは
https://www.amazon.co.jp/dp/4797375957www.amazon.co.jp
です。
以下 std:: は省略しています
error_code
このクラスはエラーの種類を表すクラスです。
OSなどのシステムや低レイヤのAPIのエラーは整数値でエラーコードが定義されている場合があります。このクラスはエラーコードを表す整数値と、その整数値がどのエラーの分類なのかを表すオブジェクト ( これがerror_category )を受け取ります。
システム等に依存したエラーコードをC++標準で使える形にアダプトしている感じです。ただし、具体的なエラーの分類やエラー整数値の値はシステム依存であるため、 error_code もまたシステム固有の状態であり、プラットフォーム間の可搬性がありません。
error_condition
機能は error_code と同じです。こちらは複数のプラットフォームで動作するプログラム(主にライブラリなど)のために、システム固有のエラー値を持つ error_code を可搬性のある値へマッピングすること意図したクラスです。
ただし、可搬性を持つ可能性がある版 error_code であり、必ず可搬性があるとは限りません。
error_code のメンバ関数 .default_error_condition() で作成することができます。
error_category
このクラスはエラーの分類を表します。 error_category という抽象クラスがあります。標準で定義されているエラーの分類は、システム、 future、 標準入出力 の3つが定義されています。それぞれのエラーの分類は error_category クラスを継承してそれぞれ実装されています。 各分類の error_category を得るには system_category() 、 future_category() 、 iostream_category() を呼び出します。
例えばとあるhoge APIで管理者権限のユーザでなければできない操作を一般ユーザで操作した時のエラーを表すエラーコードが #define EPERM 1 と定義されているとします。
また別のfoo APIで呼び出す関数が正しくないことを表すエラーコードが #define ERROR_INVALID_FUNCTION 1 と定義されているとします。
この場合、1という整数値だけでどのAPIのエラーコードが一意に定まりません。そのため、それぞれのAPIに対応する error_category を実装します。 そして error_code を生成するときに渡します。
std::error_code ec1{EPERM, hoge_category{}};
std::error_code ec2{ERROR_INVALID_FUNCTION, foo_category{}};
のようにエラー値を区別することができるようになります。具体的な error_category のメンバ関数については別途リファレンスサイトを確認してください。
WinAPIのエラーをsystem_errorにする方法
さて、いきなり話が変わりますが、具体例としてWinAPIのエラーコードを system_error で投げられる形にするサンプルです。WinAPIのエラーは GetLastError() で取得することができ、エラーカテゴリーは system_error です。
std::error_code ec{::GetLastError(), std::system_category()};
throw std::system_error se{ec};
このようにWinAPIのエラーをC++の例外にすることができます。
自分で新たにエラーコードを作成する
既にあるシステムをマッピングする場合は上記のようにすればよいのですが、新たに自分でエラーを表す整数を定義し、 system_errorで使えるようにする方法を紹介します。
整数値でエラーコードを定義することもできますが、C++11のenum classを用いて定義することもできます。新規でエラーコードを作成するのでしたら、型安全なenum classの使用をおすすめします。
例として、標準に存在している future に対するエラーコードを自作したらどうするか紹介します。
最初に、エラーの種類を表すenum classを作成します。
enum class future_errc : int { broken_promise = 1, future_already_retrived, promise_already_satisfied, no_state };
underlying typeは適宜変えてください。そしてこのenum classがエラーコードを表す特性があることを表すため、 struct std::is_error_code_enum<typename Errc> の特殊化を行います。
namespace std { template <> struct is_error_code_enum<future_errc> : true_type {}; }
次に、このエラーを分類を表す error_category を実装します。
class future_cat : public std::error_category { public: char const* name() const noexcept override { return "future"; } std::string message(int ec) const override { switch(static_cast<future_errc>(ec)) { case future_errc::broken_promise: return "future_error: broken promise"; case future_errc::future_already_retrived: return "future_error: future already retrived"; case future_errc::promise_already_satisfied: return "future_error: broken promise"; case future_errc::no_state: return "future_error: no state"; default: return "bad futurecat code"; } } } std::error_category const& future_category() noexcept { static future_cat obj; return obj; }
future_cat はエラー整数値をメッセージにする message 関数とエラー分類を表す文字列を返す name 関数を持ちます。
またこれを得るためのファクトリー future_category() を定義しました。状態を持たないため1つのオブジェクトで構わないためstaticオブジェクトを返しています。
ちなみにこの関数内のstaticオブジェクトの初期化はスレッドセーフです。
future_category() ができたため、 future_errc から error_code を作成できるようになりました。
std::error_code make_error_code(future_errc e) noexcept { return std::error_code{static_cast<int>(e), future_category()}; }
これで完了となります!更に可搬性を持たせるために error_condition にも対応したい場合は
struct std::is_error_condition_enum<typename Errc>の特殊化std::error_condition std::make_error_condition(future_errc e) noexceptの実装
を実装すれば対応できます。 error_condition には error_code と同じコンストラクタがあります。 この2つの実装は error_code と名前が違うだけで、やっていることは同じのためソースコードを省略します。
まとめ
std::error_codeはシステム固有のエラーコード(エラーの数値とそのエラーの分類)を表すstd::error_conditionはstd::error_codeと同じエラーコードを表すが、可搬性を持つ可能性がある点が異なるstd::error_categoryはstd::error_codeの実装にも用いられており(もちろん単体でも使える)、エラー整数値がどのエラー分類に所属しているかを表す