まえがき
<system_error>
とエラー処理について説明している「C ++ 0xでのシステムエラーサポート」というタイトルの既によく知られている一連の記事を翻訳するかどうか、長い間考えていました。 一方では、2010年に書かれており、彼らは単に私を死体愛好家とみなすことができ、他方では、RuNetにはこのトピックに関する情報がほとんどなく、多くのかなり最近の記事はこのサイクルを参照しており、この関連性を失わないことを示唆しています日。
そのため、この作品をアブレの
翻訳者の経験がなく、一般的に5月の英語は問題ないことをすぐに警告します。 そして悔しさ。 ですから、できればPMでのあなたの批判と提案に喜んでいます。
それでは始めましょう。
パート1
はじめに
C ++ 0xの標準ライブラリの新機能には、
<system_error>
という小さなヘッダーファイルがあります。 システムエラーを突然管理するためのツールセットを提供します。
定義されている主なコンポーネントは次のとおりです。
-
class error_category
-
class error_code
-
class error_condition
-
class system_error
-
enum class errc
私はこのモジュールの設計に手を携えていたので、一連の記事で、外観、歴史、およびそのコンポーネントの使用目的の理由についてお話します。
入手先
Boostには、完全な実装とC ++ 03のサポートが含まれています。 これはおそらく、現時点での移植性に関して最も実績のある実装だと思います。 もちろん、
std::
ではなく、
boost::system::
と記述する必要があります。
実装はGCC 4.4以降に含まれています。 ただし、プログラムを使用するには、-std = c ++ 0xオプションを使用してプログラムをコンパイルする必要があります。
最後に、 Microsoft Visual Studio 2010には、これらのクラスの実装が含まれています[ただし制限があります] 。 主な制限は、
system_category()
が意図したとおりにWin32エラーを表示しないことです。 これが何を意味するかについての詳細は後述します。
(これらは、私が知っている実装にすぎないことに注意してください。他にもあるかもしれません)。
[翻訳者のメモ:もちろん、この情報は古くなっていましたが、現在
<system_error>
は最新の標準ライブラリの不可欠な部分です]
短いレビュー
以下は、一言で
<system_error>
に定義されているタイプとクラスです。
-
class error_category
基本クラス。エラーのソース、およびエラーコードと条件のカテゴリを決定するために使用されます。 -
lass error_code
操作(システムコールなど)によって返された特定のエラー値を表します。 -
class error_condition
は、コードでテストし、場合によっては対応したいものです。 -
class system_error
-throw / catchを使用してエラーがスローされたときにerror_code
をラップするerror_code
に使用される例外。 -
enum class errc
は、POSIXから継承された一般的なエラー条件のセットです。 -
is_error_code_enum<>, is_error_condition_enum<>, make_error_code, make_error_condition
エラー値を持つ列挙をerror_code
またはerror_condition
変換するメカニズム -
generic_category()
-errc
基づいてエラーコードと条件を分類するために使用されるカテゴリオブジェクトを返します。 -
system_category()
-オペレーティングシステムから発生する[分類]エラーコードに使用されるカテゴリオブジェクトを返します。
原則
このセクションでは、モジュールの設計で従った基本原則のいくつかをリストします。 (残りの参加者について話すことはできません)。 ほとんどのソフトウェアプロジェクトと同様に、それらの一部は最初から目標であり、一部はプロセスで発生しました。
すべてのエラーが例外的ではありません。
簡単に言えば、例外は常にエラーを処理する正しい方法ではありません。 (一部のサークルでは、この声明は矛盾していますが、その理由は本当にわかりません。)
たとえば、ネットワークプログラミングでは、次のようなエラーが発生します。
- リモートIPアドレスに接続できませんでした。
- 接続が落ちました。
- IPv6ソケットを開こうとしましたが、使用可能なIPv6ネットワークインターフェイスがありません。
もちろん、例外的な状況になることもありますが、通常の制御フローの一部として同様に処理できます。 これが起こると合理的に予想する場合、これは例外ではありません。 したがって:
- IPアドレスは、ホスト名に対応するアドレスのリストの1つにすぎません。 リスト内の次のアドレスに接続してみます。
- ネットワークは信頼できません。 再接続して、N個の障害が発生した後にだけあきらめようとします。
- プログラムは、IPv4ソケットの使用に戻る場合があります。
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はほとんど同一に見えます! 何が起こっているの? これらは心のないコピー&ペーストの結果ですか?
重要なのは、あなたがそれで何をするかです。
もう一度、最初の部分で説明した内容を見てみましょう。
-
lass error_code
操作(システムコールなど)によって返された特定のエラー値を表します。 -
class 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
オブジェクトを比較する場合(つまり、==演算子または!=演算子を使用する場合)に発生します。
-
error_code
およびerror_code
完全一致がチェックされます。 -
error_condition
およびerror_condition
完全一致がチェックされます。 -
error_code
および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
への暗黙的な変換は、単一の引数コンストラクターでここで使用されます。 シンプル。 そう?
それほど単純ではありません。
物事をもう少し複雑にするいくつかの理由があります。
- 列挙値はエラー自体を示しますが、
error_condition
を構築するには、エラーのカテゴリも知っている必要があります。<system_error>
モジュールは、カテゴリを使用して複数のエラーソースをサポートします。 カテゴリは、error_code
とerror_condition
両方の属性です。 - オブジェクトは拡張可能でなければなりません。 つまり、ユーザー(および標準ライブラリの将来の拡張機能)は、独自の定数を定義できる必要があります。
- オブジェクトは、
error_code
とerror_condition
両方の定数をサポートする必要があります。enum class errc
はerror_condition
にのみ定数を提供しますが、他のユースケースでは、error_code
型の定数が必要になる場合があります。 - 最後に、列挙値から
error_code
またはerror_condition
への明示的な変換をサポートする必要があります。 移植されたプログラムは、std::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
。
-
lass error_code
-操作(システムコールなど)によって返される特定のエラー値を表します。 -
class error_condition
-コードで確認し、場合によっては対応したいもの。
これは、カスタムエラー条件のいくつかのユースケースを提供します。
- .
,getaddrinfo()
. : « , » « ».getaddrinfo()
:
- POSIX
EAI_AGAIN
EAI_NONAME
, . « » errno. ,error_category
. - Windows
WSAEAI_AGAIN
WSAEAI_NONAME
. POSIX, « »GetLastError()
. ,std::system_category()
getaddrinfo()
.
, , (, ,name_not_found_try_again
name_not_found
), API. - POSIX
- .
POSIX errno . , , , , . , .
, , . ,open()
. errnoENOENT
, .
,no_such_file_or_directory
.no_such_entry
,ENOENT
. - .
, . , :
-
not_enough_memory
-
resource_unavailable_try_again
-
too_many_files_open
-
too_many_files_open_in_system
- ...
いくつかの場所にありますが、後続のアクションは使用の各ポイントで異なります。これは、より一般的な条件があることを示しています。「システムリソースが不足している」ことを確認し、コードで応答することです。
カスタムエラー条件low_system_resources
は、他のエラー条件の組み合わせに基づいて同等になるように定義できます。これにより、次のようにチェックを作成できます。
if(low_system_resources == ec) ...
したがって、個々の検査の繰り返しを除外します。 -
以下に示すように、定義は定義に
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>
。
あとがき
残念ながら、著者の約束にもかかわらず、一連の記事は完成していませんでした。次の部分は出てきませんでした。そして、それは出てくる可能性は低いです。