C ++でのシステムエラーのサポート

まえがき



<system_error>



とエラー処理について説明している「C ++ 0xでのシステムエラーサポート」というタイトルの既によく知られている一連の記事を翻訳するかどうか、長い間考えていました。 一方では、2010年に書かれており、彼らは単に私を死体愛好家とみなすことができ、他方では、RuNetにはこのトピックに関する情報がほとんどなく、多くのかなり最近の記事はこのサイクルを参照しており、この関連性を失わないことを示唆しています日。



そのため、この作品をアブレの花崗岩で永続化することをお勧めします。



翻訳者の経験がなく、一般的に5月の英語は問題ないことをすぐに警告します。 そして悔しさ。 ですから、できればPMでのあなたの批判と提案に喜んでいます。



それでは始めましょう。



パート1



はじめに



C ++ 0xの標準ライブラリの新機能には、 <system_error>



という小さなヘッダーファイルがあります。 システムエラーを突然管理するためのツールセットを提供します。



定義されている主なコンポーネントは次のとおりです。





私はこのモジュールの設計に手を携えていたので、一連の記事で、外観、歴史、およびそのコンポーネントの使用目的の理由についてお話します。



入手先



Boostには、完全な実装とC ++ 03のサポートが含まれています。 これはおそらく、現時点での移植性に関して最も実績のある実装だと思います。 もちろん、 std::



ではなく、 boost::system::



と記述する必要があります。



実装はGCC 4.4以降に含まれています。 ただし、プログラムを使用するには、-std = c ++ 0xオプションを使用してプログラムをコンパイルする必要があります。



最後に、 Microsoft Visual Studio 2010には、これらのクラスの実装が含まれています[ただし制限があります] 。 主な制限は、 system_category()



が意図したとおりにWin32エラーを表示しないことです。 これが何を意味するかについての詳細は後述します。



(これらは、私が知っている実装にすぎないことに注意してください。他にもあるかもしれません)。



[翻訳者のメモ:もちろん、この情報は古くなっていましたが、現在<system_error>



は最新の標準ライブラリの不可欠な部分です]




短いレビュー



以下は、一言で<system_error>



に定義されているタイプとクラスです。





原則



このセクションでは、モジュールの設計で従った基本原則のいくつかをリストします。 (残りの参加者について話すことはできません)。 ほとんどのソフトウェアプロジェクトと同様に、それらの一部は最初から目標であり、一部はプロセスで発生しました。



すべてのエラーが例外的ではありません。



簡単に言えば、例外は常にエラーを処理する正しい方法ではありません。 (一部のサークルでは、この声明は矛盾していますが、その理由は本当にわかりません。)



たとえば、ネットワークプログラミングでは、次のようなエラーが発生します。





もちろん、例外的な状況になることもありますが、通常の制御フローの一部として同様に処理できます。 これが起こると合理的に予想する場合、これは例外ではありません。 したがって:





asioの場合のもう1つの要件は、非同期操作の結果を完了ハンドラーに渡す方法でした。 この場合、エラーコードをコールバックハンドラーの引数にする必要があります。



(別のアプローチは、非同期.NET BeginXYZ/EndXYZ



.NETパターンなど、ハンドラー内で例外を再構築する手段を提供することです。私の意見では、この設計は複雑さを増し、APIのエラーを起こしやすくします。)



[翻訳者注:このようなツールはstd::exception_ptr



C ++ 11のstd::exception_ptr



なりました]




最後になりましたが、コードサイズとパフォーマンスの制限により、一部の領域では例外を使用できません。



一般に、教訓的ではなく、実用的である必要があります。 エラーメカニズムを使用することは、明確さ、正確さ、制限、さらには個人的な好みの観点からも最適です。 多くの場合、例外とエラーコードを判断するための正しい基準は、その使用方法です。 これは、システムエラーの表現が両方の[オプション]をサポートする必要があることを意味します



エラーはいくつかのソースから発生します。



C ++ 03標準は、エラーコードのソースとしてerrnoを認識します。 これは、stdio関数、一部の数学関数などで使用されます。



POSIXプラットフォームでは、多くのシステム操作がerrnoを使用してエラーを送信します。 POSIXは、これらのケースをカバーするために追加のerrnoエラーコードを定義しています。



一方、WindowsはC標準ライブラリ以外ではerrnoを使用せず、Windows API呼び出しは通常GetLastError()



介してエラーを報告します。



ネットワークプログラミングの観点では、関数getaddrinfo



ファミリはPOSIXで独自のエラーコードセット(EAI _...)を使用しますが、WindowsではGetLastError()



名前空間を共有します。 他のライブラリ(SSL、正規表現など)を統合するプログラムでは、他のカテゴリのエラーコードが発生します。



プログラムは、これらのエラーコードを一貫した方法で管理できる必要があります。 特に、操作を組み合わせて高レベルの抽象化を作成できる[方法]に興味があります。 システムコール、 getaddrinfo



、SSL、および正規表現を1つのAPIに組み合わせて、このAPIのユーザーにエラーコードの種類の「爆発」を処理させることはできません。 このエラーの新しいソースをこのAPIの実装に追加しても、インターフェースは変更されません。



カスタム拡張機能



標準ライブラリのユーザーは、独自のエラーソースを追加できる必要があります。 この機能は、単にサードパーティのライブラリを統合するために使用できますが、より高いレベルの抽象化を作成したいという欲求とも関連しています。 HTTPなどのプロトコルの実装を開発するとき、RFCで定義されているエラーに対応する一連のエラーコードを追加できるようにしたいと考えています。



元のエラーコードを保存する



これは私の最初の目標の1つではありませんでした。標準はよく知られた一連のエラーコードを提供すると考えました。 システム操作がエラーを返した場合、ライブラリはエラーを既知のコードに変換する必要があります(そのようなマッピングが意味をなす場合)。



幸いなことに、誰かが私のアプローチの問題を指摘してくれました。 エラーコードの翻訳は情報をリセットします。メインシステムコールによって返されたエラーは失われます。 これは、プログラム管理フローの観点からはそれほど重要ではないかもしれませんが、プログラムのサポートにとって非常に重要です。 プログラマーが標準化されたエラーコードをログとトレースに使用することは疑いの余地がなく、初期エラーは問題の診断に不可欠です。



この最後の原則は、第2部のテーマであるerror_code



vs error_condition



ます。 連絡を取り合いましょう。



パート2



error_code vs error_condition



C ++ 0x標準の1000ページ以上から、カジュアルな読者は1つのことに気付くはずです:error_codeとerror_conditionはほとんど同一に見えます! 何が起こっているの? これらは心のないコピー&ペーストの結果ですか?



重要なのは、あなたがそれで何をするかです。



もう一度、最初の部分で説明した内容を見てみましょう。





クラスは目的が異なるため、クラスは異なります。 例として、 create_directory()



という仮想関数を考えてみましょう。



 void create_directory ( const std::string & pathname, std::error_code & ec );
      
      





次のように呼び出します:



 std::error_code ec; create_directory("/some/path", ec);
      
      





操作は、さまざまな理由で失敗する場合があります。たとえば、





失敗の原因が何であれ、 create_directory()



関数が制御を返すと、 ec



変数にはOS固有のエラーコードが含まれます。 一方、呼び出しが成功した場合、 ec



はゼロ値があります。 これは、ゼロが成功を示し、他の値がエラーを示す場合、伝統へのオマージュです(errnoおよびGetLastError()



使用されますGetLastError()







操作が成功したか失敗したかだけに関心がある場合は、 error_code



簡単にbool



に変換されるという事実を使用できます。



 std::error_code ec; create_directory("/some/path", ec); if(!ec) { //Success. } else { //Failure. }
      
      





ただし、「ディレクトリが既に存在する」エラーを確認することに興味があるとします。 このエラーが発生した場合、仮想プログラムが引き続き機能する可能性があります。 これを実装してみましょう。



 std::error_code ec; create_directory("/some/path", ec); if(ec.value() == EEXIST) //No! ...
      
      





このコードは間違っています。 POSIXプラットフォームで利益を得ることができますが、 ec



にはプラットフォーム固有のエラーが含まれることを忘れないでください。 Windowsでは、ほとんどの場合、エラーはERROR_ALREADY_EXISTS



なります。 (さらに悪いことに、コードはエラーコードカテゴリをチェックしませんが、これについては後で説明します。)



経験則: error_code::value()



を呼び出すと、何か間違ったことをしていることになります。



したがって、プラットフォームEEXIST



エラーコードEEXIST



またはERROR_ALREADY_EXISTS



)があり、それを[プラットフォームに依存しない]エラー条件 (「ディレクトリは既に存在します」)にマッピングします。 はい、そうです、 error_condition



が必要error_condition







error_codeとerror_conditionの比較



これは、 error_condition



オブジェクトとerror_condition



オブジェクトを比較する場合(つまり、==演算子または!=演算子を使用する場合)に発生します。





プラットフォームec



エラーコードを、「ディレクトリが既に存在する」エラーを表すerror_condition



オブジェクトと比較する必要があることが明らかになったことを願っています。 この場合のために、C ++ 0xはstd::errc::file_exists



提供します。 つまり、次のように書く必要があります。



 std::error_code ec; create_directory("/some/path", ec); if(std::errc::file_exists == ec) ...
      
      





これは、標準ライブラリの開発者がエラーコードEEXIST



またはERROR_ALREADY_EXISTS



とエラー条件std::errc::file_exists



同等性を定義したために機能します。 後で、対応する同等の定義を使用して独自のエラーコードと条件を追加する方法を示します。



(正確には、 std::errc::file_exists



は、 enum class errc



列挙値の1つです。現時点では、列挙されたstd::errc::*



値をerror_condition



定数のラベルと考える必要があります。次のパートでは、その仕組みを説明します。)



確認できる条件はどのようにしてわかりますか?



C ++ 0xのいくつかの新しいライブラリ関数には、エラー条件セクションがあります。 これらのセクションには、 error_condition



定数と、同等のエラーコードが生成される条件がリストされています。



ちょっとした歴史



元のerror_code



クラスは、ファイルシステムライブラリとネットワークライブラリの補助コンポーネントとしてTR2に提案されました。 その設計では、 error_code



プラットフォーム固有のエラーに対応するように、 error_code



定数が実装されました。 一致が不可能な場合、または複数の一致がある場合、ライブラリ実装はプラットフォーム固有のエラーを標準のerror_code



ます。



電子メールのディスカッションで、元のエラーコードを保持することの価値について学びました。 その後、 generic_error



クラスのプロトタイプが作成されましたが、私には向いていませんでした。 generic_error



名前をerror_condition



に変更すると、満足のいく解決策が見つかりました。 私の経験では、ネーミングはコンピューターサイエンスで最も難しい問題の1つであり、適切な名前を選択することが主な仕事です。



次のパートでは、 enum class errc



error_condition



の定数セットとして機能させるメカニズムを見てerror_condition



ます。



パート3



クラス定数としての列挙値



見てきたように、ヘッダーファイル<system_error>



は、次のようにclass enum errc



を定義しています。



 enum class errc { address_family_not_supported, address_in_use, ... value_too_large, wrong_protocol_type, };
      
      





列挙値はerror_condition



定数です:



 std::error_code ec; create_directory("/some/path", ec); if(std::errc::file_exists == ec) ...
      
      





明らかに、 errc



からerrc



への暗黙的な変換は、単一の引数コンストラクターでここで使用されます。 シンプル。 そう?



それほど単純ではありません。



物事をもう少し複雑にするいくつかの理由があります。





そのため、次の行が正しいのは事実です。



 if(std::errc::file_exists == ec)
      
      





暗黙的にerror_condition



からerrc



に変換され、さらにいくつかのステップがあります。



ステップ1:列挙された値がエラーコードか条件かを判断する



列挙型を登録するには、2つのテンプレートが使用されます。



 template<class T> struct is_error_code_enum: public false_type {}; template<class T> struct is_error_condition_enum: public false_type {};
      
      





is_error_code_enum<>



を使用して型を登録すると、暗黙的にerror_code



変換できます。 同様に、 is_error_condition_enum<>



を使用して型を登録した場合、暗黙的にerror_condition



変換できます。 デフォルトでは、型は変換なしで登録されます(したがって、上記のfalse_type



使用します)が、 enum class errc



次のように登録されます。



 template<> struct is_error_condition_enum<errc>: public true_type {};
      
      





暗黙的な変換は、条件付きで許可された変換コンストラクターを使用して実行されます。 これはおそらくSFINAEを使用して実装されますが、簡単にするために次のように考える必要があります。



 class error_condition { ... //Only available if registered //using is_error_condition_enum<>. template<class ErrorConditionEnum> error_condition(ErrorConditionEnum e); ... }; class error_code { ... //Only available if registered //using is_error_code_enum<>. template<class ErrorCodeEnum> error_code(ErrorCodeEnum e); ... };
      
      





したがって、私たちが書くとき:



 if(std::errc::file_exists == ec)
      
      





コンパイラは、次の2つのオーバーロードから選択します。



 bool operator == ( const error_code & a, const error_code & b ); bool operator == ( const error_code & a, const error_condition & b );
      
      





彼は後者を選択します。これは、 error_condition



変換コンストラクターが使用可能ですが、 error_code



使用できないためです。



ステップ2:エラー値をエラーカテゴリに一致させる



error_condition



オブジェクトには、値とカテゴリの2つの属性が含まれています。 コンストラクターに到達したので、コンストラクターを適切に初期化する必要があります。



これは、 make_error_condition()



関数を呼び出すコンストラクターのおかげで実現します。

カスタム拡張の可能性は、 ADLメカニズムを使用して実装されます。 もちろん、 errc



std



、ADLは同じ場所でmake_error_condition()



を見つけstd







make_error_condition()



の実装は簡単です:



 error_condition make_error_condition(errc e) { return error_condition ( static_cast<int>(e), generic_category() ); }
      
      





ご覧のとおり、この関数は2つの引数を持つerror_condition



コンストラクターを使用して、エラー値とカテゴリーの両方を明示的に指定します。



(正しく登録された列挙型の) error_code



変換コンストラクターerror_code



場合、呼び出される関数はmake_error_code()



ます。 それ以外の場合、 error_code



error_condition



構造は同じです。



error_codeまたはerror_conditionへの明示的な変換



error_code



は主にプラットフォーム固有のエラーで使用することを目的としていますが、移植可能なコードはerrc



列挙値からerror_code



を作成するerror_code



があります。 このため、[関数] make_error_code(errc)



およびmake_error_condition(errc)



ます。 ポータブルコードでは、次のように使用できます。



 void do_foo(std::error_code & ec) { #if defined(_WIN32) //Windows implementation ... #elif defined(linux) //Linux implementation ... #else //do_foo not supported on this platform ec = make_error_code(std::errc::not_supported); #endif }
      
      







もう少し歴史



最初は、 <system_error>



error_code



定数がオブジェクトとして定義されていました。



 extern error_code address_family_not_supported; extern error_code address_in_use; ... extern error_code value_too_large; extern error_code wrong_protocol_type;
      
      





LWGは、多数のグローバルオブジェクトのコストを心配し、代替ソリューションを要求しました。 constexpr



の使用を検討しましたが、 <system_error>



他のいくつかの側面と互換性がconstexpr



ました。 したがって、使用可能な最良の設計であるため、列挙からの変換のみが残りました。



次に、独自のエラーコードと条件を追加する方法を示します。



パート4



カスタムエラーコードを作成する



最初の部分で述べたように、 <system_error>



原則の1つは拡張性です。これは、今説明したメカニズムを使用して、独自のエラーコードを特定できることを意味します。



このセクションでは、何をする必要があるかを説明します。動作例の基礎として、HTTPライブラリを作成していて、HTTPステータスコードに対応するエラーが必要であるとします



ステップ1:エラー値を決定する



最初に、エラー値のセットを定義する必要があります。C ++ 0xを使用する場合は、class enum



同様のものを使用できますstd::errc







 enum class http_error { continue_request = 100, switching_protocols = 101, ok = 200, ... gateway_timeout = 504, version_not_supported = 505 };
      
      





エラーには、HTTPステータスコードに従って値が割り当てられます。この重要性は、エラーコードの使用に関して明らかになります。どの値を選択しても、エラーにはゼロ以外の値が必要です。あなたが思い出すように、オブジェクト<system_error>



は、ゼロが成功を意味する規則を使用しますキーワードをドロップすることにより、



通常の(つまり、C ++ 03互換)を使用できますenum



class







 enum http_error { ... };
      
      





注: 最初のものが列挙された値の名前をクラススコープで囲むという事実とはclass enum



異なりenum



ます[2番目はそれらをグローバルスコープに「スロー」します]列挙値にアクセスするには、例えば、クラスの名前を指定する必要がありますhttp_error::ok



通常enum



の名前空間[ namespace



]
ラップすることにより、この動作をエミュレートできます



 namespace http_error { enum http_error_t { ... }; }
      
      





この例の残りでは、を使用しますenum class



名前空間のアプローチは、読者の課題として残ります。



[翻訳者注:実際には、範囲が異なるだけでなくenum class



、列挙値から他の型への暗黙的な変換も禁止されています]




ステップ2:クラスerror_categoryを定義する



オブジェクトerror_code



は、エラー値とカテゴリで構成されます。エラーカテゴリは、この列挙値の具体的な意味を定義します。たとえば、100はhttp_error::continue_request



、およびstd::errc::network_down



(LinuxではENETDOWN)の両方を意味する場合があります。



新しいカテゴリを作成するには、次からクラスを継承する必要がありますerror_category







 class http_category_impl: public std::error_category { public: virtual const char * name() const; virtual std::string message(int ev) const; };
      
      





現時点では、このクラスは純粋な仮想関数のみを実装しますerror_category







ステップ3:カテゴリーに人間が読める名前を付ける



仮想関数error_category::name()



は、カテゴリを識別する文字列を返す必要があります。



 const char * http_category_impl::name() const { return "http"; }
      
      





この名前は、エラーコードをに書き込むときにのみ使用されるため、完全に一意である必要はありませんstd::ostream



ただし、このプログラムのフレームワーク内で一意にすることが望ましいでしょう。



ステップ4:エラーコードを文字列に変換する



この関数error_category::message()



は、エラー値をそれを説明する文字列に変換します。



 std::string http_category_impl::message(int ev) const { switch(ev) { case http_error::continue_request: return "Continue"; case http_error::switching_protocols: return "Switching protocols"; case http_error::ok: return "OK"; ... case http_error::gateway_timeout: return "Gateway time-out"; case http_error::version_not_supported: return "HTTP version not supported"; default: return "Unknown HTTP error"; } }
      
      





関数を呼び出すときにerror_code::message()



error_code



今度は、エラーメッセージを取得するには、前述の仮想関数の原因となります。



これらのエラーメッセージは自己完結型でなければならないことに注意してください。追加のコンテキストが使用できないプログラムのその時点で、それらを(たとえば、ログファイルに)書き込むことができます。「挿入」エラーメッセージを使用する既存のAPIをラップする場合、独自のメッセージを作成する必要があります。たとえば、HTTP APIがメッセージ文字列を使用する場合、"HTTP version %d.%d not supported"



同等のオフラインメッセージはになります"HTTP version not supported"







モジュール<system_error>



これらのメッセージのローカライズに関しては何の助けも提供しません。標準ライブラリのエラーカテゴリからのメッセージは、現在のロケールに基づいている可能性があります。プログラムでローカライズが必要な場合は、同じアプローチを使用することをお勧めします。



少し歴史: LWGはローカライズの必要性を認識していましたが、ローカリゼーションと拡張性を十分に一致させる設計はありませんでした。



ステップ5:一意のカテゴリ識別



継承されるオブジェクトの識別子は、error_category



そのアドレスによって決まります。これは、あなたが書くとき:



 const std::error_category & cat1 = ...; const std::error_category & cat2 = ...; if(cat1 == cat2) ...
      
      





条件if



は、次のように評価されます。



 if(&cat1 == &cat2) ...
      
      





標準ライブラリによって設定された例に従って、カテゴリオブジェクトへのリンクを返す関数を提供する必要があります。



 const std::error_category & http_category();
      
      





この関数は、常に同じオブジェクトへの参照を返す必要があります。これを行う1つの方法は、ソースコードファイルでグローバルオブジェクトを定義し、それへのリンクを返すことです。



 http_category_impl http_category_instance; const std::error_category & http_category() { return http_category_instance; }
      
      





ただし、グローバル変数はモジュール間の初期化順序で問題を引き起こします。別のアプローチは、ローカルの静的変数を使用することです:



 const std::error_category & http_category() { static http_category_impl instance; return instance; }
      
      





この場合、カテゴリオブジェクトは最初の使用時に初期化されます。C ++ 0xは、初期化がスレッドセーフであることも保証します。(C ++ 03はそのような保証を与えませんでした)。



履歴:設計の初期段階では、整数または文字列を使用してカテゴリを識別することを検討しました。このアプローチの主な問題は、拡張性と組み合わせた一意性を確保することでした。カテゴリが整数または文字列で識別される場合、2つの無関係なライブラリ間の衝突を防ぐにはどうすればよいですか?オブジェクトIDを使用すると、異なるカテゴリが同じIDを持つことを防ぐためにリンカーが活用されます。さらに、基本クラスへのポインターを保存することで、error_codesを多態性にする一方で、それらをコピー可能な値型として保持できます。



ステップ6:列挙型からerror_codeをビルドする



パート3で示したように、実装で<system_error>



make_error_code()



、エラー値をカテゴリに関連付けるために名前を持つ関数が必要です。HTTPエラーの場合、この関数は次のようになります。



 std::error_code make_error_code(http_error e) { return std::error_code ( static_cast<int>(e), http_category() ); }
      
      





完全を期すために、buildと同等の関数も提供する必要がありますerror_condition







 std::error_condition make_error_condition(http_error e) { return std::error_condition ( static_cast<int>(e), http_category() ); }
      
      





実装で<system_error>



はADLを使用してこれらの関数を検出するため、typeと同じ名前空間に配置する必要がありますhttp_error







手順7:暗黙的にerror_codeに変換するための書き込み



列挙値http_error



を定数としてerror_code



使用するには、テンプレートを使用して変換コンストラクターを有効にしますis_error_code_enum







 namespace std { template<> struct is_error_code_enum<http_error>: public true_type {}; }
      
      







ステップ8(オプション):デフォルトのエラー条件を判別する



説明したエラーの一部には、からの同様のエラー条件が含まれている場合がありますerrc



たとえば、HTTP 403 Forbiddenステータスコードはと同じ意味std::errc::permission_denied



です。



仮想関数をerror_category::default_error_condition()



使用すると、特定のエラーコードに相当するエラー条件を定義できます。(同等性の定義については、第2部で説明しました。)HTTPエラーの場合、次のように記述できます。



 class http_category_impl: private std::error_category { public: ... virtual std::error_condition default_error_condition(int ev) const; }; ... std::error_condition http_category_impl::default_error_condition(int ev) const { switch(ev) { case http_error::forbidden: return std::errc::permission_denied; default: return std::error_condition(ev, *this); } }
      
      







この仮想関数を再定義しないことに決めた場合error_condition



、と同じエラーとカテゴリになりますerror_code



上記の例のように、これがデフォルトの動作です。



エラーコードを使用する



これで、エラーを設定するときのように、列挙値http_error



を定数として使用できerror_code



ます。



 void server_side_http_handler ( ..., std::error_code & ec ) { ... ec = http_error::ok; }
      
      





そしてそれをチェックするとき:



 std::error_code ec; load_resource("http://some/url", ec); if(http_error::ok == ec) ...
      
      





[翻訳者のメモ:この実装では、上記の原則は機能しません-ゼロ値=成功-それぞれ、キャストbool



も機能しません]




エラー値はHTTPステータスコードに基づいているためerror_code



、応答から直接設定することもできます



 std::string load_resource ( const std::string & url, std::error_code & ec ) { //send request ... //receive response ... int response_code; parse_response(..., &response_code); ec.assign(response_code, http_category()); ... }
      
      





さらに、このメソッドを使用して、既存のライブラリによって生成されたエラーをラップできます。



最後に、手順8で等価関係を決定した場合、次のように記述できます。



 std::error_code ec; data = load_resource("http://some/url", ec); if(std::errc::permission_denied == ec) ...
      
      





エラー状態の正確な原因を知る必要はありません。パート2で説明しhttp_error::forbidden



たように、情報が失われないように、元のエラーコード(たとえば、)が保存されます。



次のパートでは、作成および使用方法を示しますerror_condition







パート5



カスタムエラー条件の作成



モジュールの拡張性は<system_error>



エラーコードに限定されず、拡張するerror_condition



こともできます。



独自のエラー条件を作成する理由



この質問に答えるために、のは間違いに戻ってみようerror_code



error_condition











これは、カスタムエラー条件のいくつかのユースケースを提供します。





以下に示すように、定義は定義にerror_condition



似ていますerror_code







ステップ1:エラー値を決定する



enum



同様に、エラー値を作成する必要がありますstd::errc







 enum class api_error { low_system_resources = 1, ... name_not_found, ... no_such_entry };
      
      





使用する実際の値は重要ではありませんが、それらが異なっていてゼロ以外であることを確認する必要があります。



ステップ2:クラスerror_categoryを定義する



オブジェクトerror_condition



は、エラー値とカテゴリで構成されます。新しいカテゴリを作成するには、次からクラスを継承する必要がありますerror_category







 class api_category_impl: public std::error_category { public: virtual const char * name() const; virtual std::string message(int ev) const; virtual bool equivalent(const std::error_code & code, int condition) const; };
      
      





ステップ3:カテゴリーに人間が読める名前を付ける



仮想関数error_category::name()



は、カテゴリを識別する文字列を返す必要があります。



 const char * api_category_impl::name() const { return "api"; }
      
      





ステップ4:エラー状態を文字列に変換する



この関数error_category::message()



は、エラー値をそれを説明する文字列に変換します。



 std::string api_category_impl::message(int ev) const { switch(ev) { case api_error::low_system_resources: return "Low system resources"; .. } }
      
      





ただし、ユースケースによっては、呼び出しerror_condition::message()



が発生する可能性は低くなります。この場合、略語を使用して次のように書くことができます。



 std::string api_category_impl::message(int ev) const { return "api error"; }
      
      





ステップ5:エラー等価を実装する



仮想関数はerror_category::equivalent()



、エラーコードと条件の等価性を判断するために使用されます。この機能には2つのオーバーロードがあります。最初のもの:



 virtual bool equivalent(int code, const error_condition & condition) const;
      
      





error_code



現在のカテゴリと任意の同等性を確立するために使用されますerror_condition



2番目のオーバーロード:



 virtual bool equivalent(const error_code & code, int condition) const;
      
      





error_condition



現在のカテゴリと任意のの間の等価性定義しますerror_code



エラー条件を作成しているため、2番目のオーバーロードをオーバーライドする必要があります。



同等性の定義は単純です条件と同等にしtrue



たいerror_code



場合はreturn、そうでない場合はreturn false



です。



プラットフォーム固有のエラーを無視する場合はerror_category::equivalent()



、次のように実装できます。



 bool api_category_impl::equivalent(const std::error_code & code, int condition) const { switch(condition) { ... case api_error::name_not_found: #if defined(_WIN32) return code == std::error_code(WSAEAI_NONAME, system_category()); #else return code == std::error_code(EAI_NONAME, getaddrinfo_category()); #endif ... default: return false; } }
      
      





(明らかに、getaddrinfo_category()



どこかで定義する必要もあります。)



チェックは複雑になる可能性があり、他の定数を再利用することもできますerror_condition







 bool api_category_impl::equivalent(const std::error_code & code, int condition) const { switch(condition) { case api_error::low_system_resources: return code == std::errc::not_enough_memory || code == std::errc::resource_unavailable_try_again || code == std::errc::too_many_files_open || code == std::errc::too_many_files_open_in_system; ... case api_error::no_such_entry: return code == std::errc::no_such_file_or_directory; default: return false; } }
      
      





ステップ6:一意のカテゴリ識別



カテゴリオブジェクトへの参照を返す関数を定義する必要があります。



 const std::error_category & api_category();
      
      





常に同じオブジェクトへの参照を返します。エラーコードと同様に、グローバル変数を使用できます。



 api_category_impl api_category_instance; const std::error_category & api_category() { return api_category_instance; }
      
      





または、C ++ 0xの静的スレッドセーフ変数を使用できます。



 const std::error_category & api_category() { static api_category_impl instance; return instance; }
      
      





ステップ7:列挙型からerror_conditionを構築する



実装で<system_error>



make_error_code()



、エラー値をカテゴリに関連付けるために名前を持つ関数が必要です。



 std::error_condition make_error_condition(api_error e) { return std::error_condition ( static_cast<int>(e), api_category() ); }
      
      





完全を期すために、buildと同等の関数を定義する必要もありますerror_code



これは読者のための演習として残しておきます。



ステップ8:error_conditionへの暗黙的な変換のための書き込み



最後に、列挙値api_error



を定数として使用できるようにerror_condition



、テンプレートを使用して変換コンストラクターを有効にしis_error_condition_enum



ます。



 namespace std { template<> struct is_error_condition_enum<api_error>: public true_type {}; }
      
      





エラー条件を使用する



これで、列挙値api_error



を定数error_condition



として使用できるほか、次で定義されたを使用できますstd::errc







 std::error_code ec; load_resource("http://some/url", ec); if(api_error::low_system_resources == ec) ...
      
      





何度か言ったように、元のエラーコードは保存され、情報は失われません。このエラーコードは、オペレーティングシステムからのものか、独自のエラーカテゴリを持つHTTPライブラリからのものかは関係ありません。とにかく、カスタムエラー条件も同様に機能する可能性があります。



次の、おそらく最後の部分で、を使用するAPIの作成方法を説明します<system_error>







あとがき



残念ながら、著者の約束にもかかわらず、一連の記事は完成していませんでした。次の部分は出てきませんでした。そして、それは出てくる可能性は低いです。



All Articles