本文介绍如何使用 C++ Error Code 和 Error Condition。
示例代码 https://github.com/pdeantihuman/cpp-error-sample

Error Code

:::info C++的Error Code类似于一个tuple {int, error_category},其中 int是错误码,而 error_category则携带错误码的额外信息,例如错误码类型的名称,错误码表示的信息。 ::: 本节介绍如何自定义 C++ Error Code
首先,定义一个枚举类 FlightsErrc,表示不同的错误类型。
注意,不要有等于0的枚举量,等于0应当意味着操作成功。

  1. enum class FlightsErrc {
  2. // no 0
  3. NonexistentLocations = 10, // requested airport doesn't exist
  4. DatesInThePast, // booking flight for yesterday
  5. InvertedDates, // returning before departure
  6. NoFlightsFound = 20, // did not find any combination
  7. ProtocolViolation = 30, // e.g., bad XML
  8. ConnectionError, // could not connect to server
  9. ResourceError, // service run short of resources
  10. Timeout, // did not respond in time
  11. };

第二步,定义模板。添加这个模板后,std::error_code 才会将 FlightsErrc认作一个错误码。

namespace std {
template <> struct is_error_code_enum<FlightsErrc> : true_type {};
} // namespace std

:::danger 需要注意的是,这里如果你用的关键词是 class ,那么继承须为 : public true_type ::: 第三步,声明 make_error_code函数。需要注意的是,make_error_code所在的 namespace 必须是 FlightsErrc 可见的 namespace,std::error_code 使用 ADL 机制寻找 make_error_code 函数。如果你不清楚这是何种含义,将 make_error_codeFlightsErrc声明在同一作用域内。

std::error_code make_error_code(FlightsErrc);

:::info 完成以上三步后,你的错误码就已经在类型方面合格了。编译器已经不会在类型方面抱怨了。 :::

  std::error_code ec = FlightsErrc::NoFlightsFound;
  assert(ec != FlightsErrc::ResourceError);

但是以上只是解决了类型问题,接下去我们看 make_error_code 如何实现。

namespace { // anonymous namespace

struct FlightsErrCategory : std::error_category {
  const char *name() const noexcept override;
  std::string message(int ev) const override;
};

} // anonymous namespace

首先,我们定义一个 FlightsErrCategory类,继承 std::error_category
第二,实现 std::category的方法

namespace { // anonymous namespace
const char *FlightsErrCategory::name() const noexcept { return "flights"; }

std::string FlightsErrCategory::message(int ev) const {
  switch (static_cast<FlightsErrc>(ev)) {
  case FlightsErrc::NonexistentLocations:
    return "nonexistent airport name in request";

  case FlightsErrc::DatesInThePast:
    return "request for a date from the past";

  case FlightsErrc::InvertedDates:
    return "requested flight return date before departure date";

  case FlightsErrc::NoFlightsFound:
    return "no filight combination found";

  case FlightsErrc::ProtocolViolation:
    return "received malformed request";

  case FlightsErrc::ConnectionError:
    return "could not connect to server";

  case FlightsErrc::ResourceError:
    return "insufficient resources";

  case FlightsErrc::Timeout:
    return "processing timed out";

  default:
    return "(unrecognized error)";
  }
}
} // anonymous namespace

定义一个全局变量并返回。 :::danger 需要注意的是,theFlightsErrCategory必须是全局唯一的。我们通过比较 error category 判断错误码的类型。 :::

namespace { // anonymous namespace
const FlightsErrCategory theFlightsErrCategory{};
} // anonymous namespace

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

Error Condition

:::info Error Condition 的功能是可以将不同库的 Error Code 识别为一个统一的 Error Condition。 ::: 定义类型方面,Error Code和 Error Condition 没有太大差别。

// 定义一个枚举类表示不同的 Error Condition
enum class FailureSource {
  // no 0
  BadUserInput = 1,
  SystemError = 2,
  NoSolution = 3,
};

// 定义模板将 FailureSource 识别为 std::error_condition
namespace std {
template <> struct is_error_condition_enum<FailureSource> : true_type {};
} // namespace std

// 将枚举类 FailureSource 转换为 error_condition
std::error_condition make_error_condition(FailureSource e);

我们来看实现方面
首先自定义一个 Error Category 类。

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

equivalent方法用来比较我们自定义的 Error Condition 和 error_code 。

namespace {
const char *FailureSourceCategory::name() const noexcept {
  return "failure-source";
}

std::string FailureSourceCategory::message(int ev) const {
  switch (static_cast<FailureSource>(ev)) {
  case FailureSource::BadUserInput:
    return "invalid user request";

  case FailureSource::SystemError:
    return "internal error";

  case FailureSource::NoSolution:
    return "no solution found for specified request";

  default:
    return "(unrecognized condition)";
  }
}

bool FailureSourceCategory::equivalent(const std::error_code &ec,
                                       int cond) const noexcept {
  // 先提取 Error Category
  const std::error_category &FlightsCat =
      std::error_code{FlightsErrc{}}.category();

  switch (static_cast<FailureSource>(cond)) {
  case FailureSource::BadUserInput:
    if (ec == SeatsErrc::NonexistentClass)
      return true;
    // 通过比较 category 判断错误码的类型。
    if (ec.category() == FlightsCat)
      return ec.value() >= 10 && ec.value() < 20;
    return false;

  case FailureSource::SystemError:
    if (ec == SeatsErrc::InvalidRequest || ec == SeatsErrc::CouldNotConnect ||
        ec == SeatsErrc::InternalError || ec == SeatsErrc::NoResponse)
      return true;

    if (ec.category() == FlightsCat)
      return ec.value() >= 30 && ec.value() < 40;

    return false;

  case FailureSource::NoSolution:
    if (ec == SeatsErrc::NoSeatAvailable)
      return true;
    if (ec.category() == FlightsCat)
      return ec.value() >= 20 && ec.value() < 30;
    return false;

  default:
    return false;
  }
}

}

error condition 的 error category 对象依然要求全局唯一。

namespace {
const FailureSourceCategory theFailureSourceCategory{};
}

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

参考文献

[1]: Your own error condition - Andrzej Krzemieński
[2]: Boost LEAF