lix::Succession
試しに公開してみた https://code.google.com/p/lix-failable/source/browse/ こいつですが、
例えばこういう関数を作るとして、
// 普通は失敗しない何かをする。失敗時は false を返す、失敗理由は(Win32の)GetLastError で。 BOOL DoSomething( int x);
この 『失敗理由は GetLastError()』 というのは、概念的にはその関数のシグネチャに含まれていますが、シンタックス的にはシグネチャではありません。
かといって、ここで『ErrorCodeを返す』ように変更してしまうと、エラーハンドリングの記述が直感的ではなくなる、という問題が発生してしまいます。
BOOL DoSomething( int x); // どこかで DWORD err = DoSomething( path ); if( err != NO_ERROR ){ // <- この if 文が直感的じゃない。 ... } if( err ) { // <- これ何が言いたいのかわからない、ミスか? ... }
なので、『成否を表しつつ否の場合はエラーコードを持たせることができるクラス』があれば、そのへんがすっきりするんじゃないかと。
で、作ってみたのが lix::Succession です。
これを使うと
lix::WinSuccession DoSomething( int x) { if( x < 0 ){ // ホントはなんか API を呼ぶ、その副作用で LastError が変わる。 SetLastError( ERROR_INVALID_PARAMETER ); // 失敗、 GetLastError を持たせるよ、という明示的記述。 return lix::Failed( GetLastError()); // こう書いても同じ return lix::FailedWithLastError(); // これでも同じ return lix::WinSuccession( false, GetLastError()); // これもコンパイル通っちゃうけど、 true を許しつつこれをやめさせる方法が思いつかなかった。 return false; } // 成功(boolからの変換コンストラクタがある。) // false 側を考えると、これは変換させないようにするか、ちょっと悩んだ。 return true; // どっちかっていうとこう書いてほしいな。 return lix::Succeeded(); } void sample1(){ lix::WinSuccession r = DoSomething( 1 ); assert( r.IsSucceeded()); r = DoSomething(-1); // if( r.IsFailed()) でも同じ if( !r ){ // 失敗。 // エラーコードを返す。 DWORD err = r.GetErrorCode(); // メッセージを返す。 wstring message = r.GetErrorMessage(); // ストリームにも流せたりする。(エラーメッセージが流れる) cerr << r << endl; } }
こんなふうにかけます。
lix::WinSuccession というクラステンプレートは2つのテンプレートパラメータをとり、第一パラメータがエラーコードの型、第二パラメータがそのエラーコードをどうやって文字列化するか、というポリシーです。
第二引数は lix::ErrorCodeTraits というテンプレートがデフォルトパラメータになっているため、指定しなくても構いません。
つまり lix::Succession は
template < typename ErrorCodeType, typename ErrorCategory = lix::ErrorCodeTraits<ErrorCodeType> > class Succession{ ... };
こうです。
で何度も出てきている lix::WinSuccession は DWORD と FormatMessagge をつかうポリシーでインスタンス化した lix::Succession
『俺のエラーコードは独自の emun だ!』とかいう場合もメッセージの取得さえ諦めればお手軽に、
enum CustomErrorCode { CUSTOM_OK = 0, CUSTOM_INVALID_ARG = 1, CUSTOM_SOMETHING_HAPPENS = 2 }; lix::Succession<CustomErrorCode> DoHogeHoge(int x) { if( x < 0) { return lix::Failed(CUSTOM_INVALID_ARG); } if( x > 100 ) { return lix::Failed(CUSTOM_SOMETHING_HAPPENS); } return lix::Succeeded(); } void sample2 { auto r = DoHogeHoge(1); assert( r.IsSucceeded()); r = DoHogeHoge( -1 ){ if( ! r ){ cerr << "失敗した。" << r.GetErrorCode() << endl. } }
こんなかんじで使えます。
そこで、エラーメッセージも対応するならば lix::ErrorCodeTraits
enum CustomErrorCode { CUSTOM_OK = 0, CUSTOM_INVALID_ARG = 1, CUSTOM_SOMETHING_HAPPENS = 2 }; // CustomErrorCode について lix::ErrorCodeTraits を特殊化。 namespace lix{ template<> class ErrorCodeTraits<CustomErrorCode> : public DefaultErrorCodeTraits<CustomErrorCode>{ public: static std::string GetText( code_type code){ if( code == CUSTOM_OK ) return "OK"; if( code == CUSTOM_INVALID_ARG ) return "ひきすうへんだよ?"; if( code == CUSTOM_SOMETHING_HAPPENS ) return "なんかおきた"; return "orz."; } }; } lix::Succession<CustomErrorCode> DoHogeHoge(int x) { if( x < 0) { return lix::Failed(CUSTOM_INVALID_ARG); } if( x > 100 ) { return lix::Failed(CUSTOM_SOMETHING_HAPPENS); } return lix::Succeeded(); } void sample3() { auto r = DoHogeHoge(1000 ); if( ! r ){ // これで、"なんかおきた" と表示される。 cerr << r << endl; } }
この CustomErrorCode のようにエラーコードの型からその扱いが一意に決まるならば、このような特殊化で対応可能ですが、現実的には int などの汎用の型を異なる体系のエラーコードとして使用していることがほとんどだと思います。
その場合は ErrorCategory 全体を作り、パラメータをすべて指定した typedef を通して使用することになるかと思います。
とりあえずサンプルとして Win32 の LastError 機構をつかう、
typedef Succession<DWORD, Win32ErrorCategory> WinSuccession;
と、 errno をつかう、
typedef Succession<errno_t, CRTErrorNoCategory> CRTSuccession;
を作ってみました。
winsock用とか HRESULT用とか、いろいろ ErrorCategory を作ってあげれば良い感じになるのではないかな、と。