2017-09-09 21 views
3

私はCライブラリlibusbを使用しています。 libusbメソッドはエラーを示すためにlibusb_error enum valuesを返します。私はこれらのメソッドが代わりに例外をスローするようにしたい。列挙型の戻り値をC++の例外に変換するには?

各libusbエラーには、libusb_strerror()機能で取得できる説明があります。私はこれを例外のスローされた例外のメソッドが返すようにしたい。

私の最初の試みは、エラーの説明で構成std::runtime_errorをスローするラッパー関数関係:

libusb_error wrapper(libusb_error error) { 
    if(error >= 0) return error; 
    throw std::runtime_error(libusb_strerror(error)); 
} 

をしかし、そのアプローチを使用すると、私はエラーだけでも、特定のlibusbエラーのlibusbキャッチすることはできませんを意味します。

私は、その後のlibusbエラーのstd::exceptionのテンプレートサブクラスを作った:

template<libusb_error error> 
class LIBUSBError : public std::exception { 
    using std::exception::exception; 
    virtual const char* what() const throw() { 
     return libusb_strerror(error); 
    } 
}; 

アプローチは、特定のlibusbエラーをキャッチするために私にできること。しかし、テンプレートパラメータには、私のラッパー関数内の各列挙値のために複数のステートメントを使用するために私を必要とし、定数式でなければならない:

私が書くために私を必要とせずにC++の例外へのlibusbメソッドの戻り値を変換するにはどうすればよい
// * * * 

libusb_error wrapper(libusb_error error) { 
    if(error >= 0) return error; 
    if(error == LIBUSB_ERROR_IO) { 
     throw LIBUSBError<LIBUSB_ERROR_IO>(); 
    } 
    if(error == LIBUSB_ERROR_ACCESS) { 
     throw LIBUSBError<LIBUSB_ERROR_ACCESS>(); 
    } 
    // ... etc 
} 

// * * * 

    try { 
     wrapper(libusb_open(device, &handle)); 
    } catch(LIBUSBError<LIBUSB_ERROR_ACCESS> e) { 
     std::cerr << e; 
     continue; 
    } 

// * * * 

各列挙型の値を外しますか?

+0

enum値に応じて適切な例外を格納するマップを作成することができます –

+2

エラーの種類ごとに個別のキャッチブロックが必要ですか?もしそうでなければ、1つの引数(enum値)を受け入れ、それに応じて 'std :: exception'を構築する' LIBUSBError'にctorを書くのは賢明でしょう。 – myaut

+1

エラーごとに別々のクラスが必要な場合は、それらを生成するファクトリを生成する方法について、この回答を確認してください。https://stackoverflow.com/questions/29400314/enums-and-initialising-classes/29401310#29401310 – myaut

答えて

2

私は、コンパイル時にあなたのエラーは、いくつかの範囲内にある場合switch/caseifなど

以外の値(テンプレートパラメータ)を(変数で)実行時の値の間で変換する方法がないと思います

int a = rand(); // replace with the function call that may fail 
if (a != 0) { // This check is important to let success scenario proceed quickly 
    selectThrow <100>(a); 
} 
0123:あなたはそれが好きで使用すると

template<int taError> class MyException : public std::exception { 
    using std::exception::exception; 
    virtual const char* what() const throw() override { 
    printf("It's %d\n", taError); 
    return std::exception::what(); 
    } 
}; 

template<int taError> void selectThrow(int myErr) { 
    if (myErr == taError) { 
    throw MyException<taError>(); 
    } 
    return selectThrow<taError - 1>(myErr); 
} 

template<> void selectThrow<0>(int) { 
} 

100から0は、あなたがテンプレートを使用して次の操作を行うことができます、と言います

あなたはすべての場所でifを書く避けるための機能を行うことができます。

void checkThrow(int errCode) { 
    if(errCode != 0) { 
    selectThrow<100>(errCode); 
    // If nothing has been thrown, you can raise "unknown error" exception here 
    } 
} 

しかし、あなたのエラーの範囲は、テンプレートメタプログラミングに対して大きすぎる場合は、1つのオプションが明示的に各エラーを指定すると可変長引数テンプレートですあなたが処理します。あなたは、実際のエラー名に<1, 3, 7, 15, 25>を交換する必要が上記の例では

template<typename... taOthers> void throwVariadic(int) { 
    /* throw "unexpected error" exception here */ 
} 

template<int taCur, int... taOthers> void throwVariadic(int errCode) { 
    if (taCur == errCode) { 
    throw MyException<taCur>(); 
    } 
    return throwVariadic<taOthers...>(errCode); 
} 

void checkError(int errCode) { 
    if(errCode == 0) return; 
    throwVariadic<1, 3, 7, 15, 25>(errCode); 
} 

あなたが持っている:<LIBUSB_ERROR_IO, LIBUSB_ERROR_ACCESS /*, ...*/>

最後に、あなたが過度のタイピングを避けましょうswitch/caseプラスマクロ(後者に頼るするオプションがあります)。

// Short for Handle LibUsb Error 
#define HLUE(var) case var: throw LIBUSBError<var>(); 
switch(error) { 
    default: 
    /* Throw "unknown error" exception here */ 
    case 0: return 0; 
    HLUE(LIBUSB_ERROR_IO) 
    HLUE(LIBUSB_ERROR_ACCESS) 
    /* etc for other codes */ 
} 
#undef HLUE 
+0

[この回答](https://stackoverflow.com/a/29401310)には、最初のコードの可能なパフォーマンスに関する情報が含まれています。リンクはもともと私の質問にコメントとして@myautによって投稿されました。 – metarmask

2

は、通常のアプローチが適切なstd例外から派生し、独自の例外クラスを作成することです:

class libusb_exception : public std::runtime_error { 
public: 
    libusb_exception(libusb_error error) 
     : runtime_error(libusb_strerror(error)) {} 
}; 

今ではその型のオブジェクトを投げることができます。

if (error < 0) 
    throw libusb_exception(error); 

また、格納することができ例外オブジェクトのエラー値。

もっと一般的には、C++標準ライブラリのsystem error facilityのデザインを見てください。これは、通常は数値として表示されるシステム生成エラーを処理する一般的なモデルを意図しています。

+1

このアプローチでは、libusbエラーだけを捕まえることができますが、好ましくは、直接catch節で異なるlibusbエラーを区別できるようにしたいと考えています。しかし、サブクラス化のこの方法ははるかにクリーンです。 – metarmask

0

別のレベルのインダイレクションを導入することで、少なくとも部分的に問題を解決できると思います。 @Peteベッカーが提案しlibusb_exceptionで始めてみましょう:

class libusb_exception : public std::runtime_error { 
public: 
    libusb_exception(libusb_error error) 
     : runtime_error(libusb_strerror(error)) {} 
}; 

さて、上記のクラスに基づいて、特定のlibusbの例外を意味するテンプレートを作成してみましょう:

template<libusb_error error> 
class specific_libusb_exception : public libusb_exception { 
    specific_libusb_exception() : libusb_exception(error) 
    { 
    } 
}; 

さて、あれば評価する関数を紹介しましょうlibusbエラーが取り扱いを必要とし、それがどのように特定:

void assure_libusb(libusb_error error) 
{ 
    switch(error) 
    { 
     case LIBUSB_SUCCESS: 
      return; 
     case LIBUSB_ERROR_IO: // needs specific handling 
      throw specific_libusb_exception<LIBUSB_ERROR_IO>(); 
     case LIBUSB_ERROR_OVERFLOW: // also needs specific handling 
      throw specific_libusb_exception<LIBUSB_ERROR_OVERFLOW>(); 
     default: // the rest is handled by generic libusb error handler 
      throw libusb_exception(error); 
    } 
} 

今、あなたはこのようlibusbを呼び出す:

assure_libusb(libusb_function_to_call(...)); 

せるスタックハンドラアップ、または

try { 
    assure_libusb(libusb_function_to_call(...)); 
} 
catch(specific_libusb_exception<LIBUSB_ERROR_IO> &e) 
{ 
: 
} 
catch(...) 
{ 
    throw; 
} 
+0

これでも、それぞれの列挙値を書き出す必要があります。 'switch'を使うと少し短くなります。 – metarmask

+0

あなたは特別に扱わなければならない列挙のみ書くことができます。そして、ある方法、あるいは別の方法で、あなたはそれをしなければなりません。あなたがテンプレートメタプログラミングを採用したとしても、遅かれ早かれ、条件(もしあれば、スイッチであろうとなかろうと)とenum値の両方でコード内に表示されます。さらに、すべての列挙型を1つの場所に配置すると(上記のassure_libusb関数のように)、コードを簡単に保守することができます。新しいエラーコードを追加する必要があります。 – Tomek

+0

Sergeの答えの最初の部分は、すべての列挙値を使用するのではなく、最小値と最大値だけを使用します。 – metarmask

0

私はもっとこのような何かを意味:

#include <exception> 
#include <tuple> 
#include <type_traits> 
#include <map> 

enum libusb_error { 
    LIBUSB_SUCCESS, 
    LIBUSB_ERROR_IO, 
    LIBUSB_ERROR_INVALID_PARAM, 
    LIBUSB_ERROR_ACCESS, 
    LIBUSB_ERROR_NO_DEVICE, 
    LIBUSB_ERROR_NOT_FOUND, 
    LIBUSB_ERROR_BUSY, 
    LIBUSB_ERROR_TIMEOUT, 
    LIBUSB_ERROR_OVERFLOW, 
    LIBUSB_ERROR_PIPE, 
    LIBUSB_ERROR_INTERRUPTED, 
    LIBUSB_ERROR_NO_MEM, 
    LIBUSB_ERROR_NOT_SUPPORTED, 
    LIBUSB_ERROR_OTHER 
}; 

const char *libusb_strerror(libusb_error error) { 
    if (LIBUSB_ERROR_IO) 
     return "LIBUSB_ERROR_IO"; 
    if (LIBUSB_ERROR_INVALID_PARAM) 
     return "LIBUSB_ERROR_INVALID_PARAM"; 
} 

class generic_error { 
public: 
    virtual const char* what() const throw() = 0; 
    virtual void do_throw() const = 0; 
}; 

template<libusb_error error> 
class LIBUSBError : public generic_error { 
public: 
    virtual const char* what() const throw() { 
     return libusb_strerror(error); 
    } 
    virtual void do_throw() const { 
     throw *this; 
    } 

}; 

template <class SupportedErrors = std::tuple<std::integral_constant<libusb_error, LIBUSB_ERROR_IO>, std::integral_constant<libusb_error, LIBUSB_ERROR_INVALID_PARAM>>> 
struct ErrorMap; 

template <libusb_error... Es> 
struct ErrorMap<std::tuple<std::integral_constant<libusb_error, Es>...>>{ 
    static std::map<libusb_error, generic_error*> em; 
}; 

template <libusb_error... Es> 
std::map<libusb_error, generic_error*> ErrorMap<std::tuple<std::integral_constant<libusb_error, Es>...>>::em = {{Es, new LIBUSBError<Es>{}}...}; 



libusb_error known_at_runtime() { 
    return LIBUSB_ERROR_IO; 
} 

int main() { 
    try { 
     ErrorMap<>::em[known_at_runtime()]->do_throw(); 
    } catch (LIBUSBError<LIBUSB_ERROR_IO>&) { 
    } 
} 

[live demo]

PS。申し訳ありませんがwandboxには、コードを少し鮮明にするlibusbを含める方法がありません

関連する問題