概要
C++11で <system_error>
ヘッダが追加され、その中に3つのクラス
std::error_code
std::error_category
std::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
の実装にも用いられており(もちろん単体でも使える)、エラー整数値がどのエラー分類に所属しているかを表す