【问题标题】:How to convert enum return values to C++ exceptions?如何将枚举返回值转换为 C++ 异常?
【发布时间】:2017-09-09 12:07:10
【问题描述】:

我正在使用 C 库 libusb。 libusb 方法返回libusb_error enum values 表示错误。我希望这些方法改为抛出异常。

每个 libusb 错误都有一个可以通过libusb_strerror() 函数检索的描述。我希望这是 what() 抛出异常的方法返回的内容。

我的第一次尝试涉及一个包装函数,它抛出一个带有错误描述的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 错误。但是,模板参数必须是常量表达式,这要求我对包装函数中的每个枚举值使用多个语句:

// * * *

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;
    }

// * * *

如何在不需要我写出每个枚举值的情况下将 libusb 方法的返回值转换为 C++ 异常?

【问题讨论】:

  • 您可以根据枚举值创建一个存储适当异常的地图
  • 您需要为每种类型的错误单独的 catch 块吗?如果没有,在 LIBUSBError 中编写一个 ctor 将接受一个参数(枚举值)并据此构造 std::exception 是明智的。
  • 如果您仍然希望每个错误都有单独的类,请查看有关如何生成产生它们的工厂的答案:stackoverflow.com/questions/29400314/…
  • 有多少个错误,它们的范围是多少?
  • @myaut 是的,每个错误我都需要一个单独的 catch 块。

标签: c++ c++11 exception enums


【解决方案1】:

我认为您至少可以通过引入另一个间接级别来部分解决您的问题。让我们从@Pete Becker 提出的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;
}

【讨论】:

  • 这仍然需要我写出每个枚举值。不过,使用switch 会使它稍微短一些。
  • 你只能写那些你必须专门处理的枚举。以一种或另一种方式,你将不得不这样做。即使您使用模板元编程,迟早您会在代码中看到条件(无论是 if、switch 还是其他)和枚举值。此外,将所有枚举放在一个地方(如上面的 assert_libusb 函数)使代码更易于维护 - 任何新的错误代码都只需要在那里添加。
  • Serge 的回答的第一部分没有使用所有的枚举值,只使用了最小值和最大值。
【解决方案2】:

我认为除了switch/caseif等之外,没有办法将运行时值(变量中)转换为编译时值(模板参数)。

如果您的错误在某个范围内,例如 0100,您可以使用模板执行以下操作:

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) {
}

然后你像这样使用它:

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);
}

你可以创建一个函数来避免在每个地方写if

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

但是,如果您的错误范围对于模板元编程来说太大了,那么一种选择是可变参数模板,其中明确指定您处理的每个错误。

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);
}

在上面的示例中,您需要将 &lt;1, 3, 7, 15, 25&gt; 替换为您拥有的实际错误名称:&lt;LIBUSB_ERROR_IO, LIBUSB_ERROR_ACCESS /*, ...*/&gt;

最后,可以选择使用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

【讨论】:

  • This answer 包含有关第一段代码的可能性能的一些信息。该链接最初由 @myaut 作为对我的问题的评论发布。
【解决方案3】:

通常的做法是编写自己的异常类,派生自相应的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 的设计。它旨在成为处理系统生成错误的通用模型,这些错误通常显示为数值。

【讨论】:

  • 这种方法只允许我捕获 libusb 错误,但我希望能够直接在 catch 子句中区分不同的 libusb 错误。不过,这种子类化方式要干净得多。
【解决方案4】:

我的意思更像是这样的:

#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,这会使代码更清晰

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-21
    • 1970-01-01
    • 1970-01-01
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多