Чем std::error_code отличается от std::error_condition

В С++ есть два похожих класса, https://eel.is/c++draft/syserr.errcode

error_code describes an object used to hold error code values, such as those originating from the operating system or other low-level application program interfaces.

и https://eel.is/c++draft/syserr.errcondition

error_condition describes an object used to hold values identifying error conditions.

[Note 1: error_condition values are portable abstractions, while error_code values ([syserr.errcode]) are implementation specific. — end note]

Если я хочу использовать их для своих ошибок, какой лучше использовать и почему?


Ответы (1 шт):

Автор решения: Abyx

TL;DR: для возврата ошибок - только error_code. Для проверки ошибок - можно error_condition.

Слова про "переносимые абстракции" у error_condition могут смутить разработчика. Давайте посмотрим чем отличаются интерфейсы этих классов: у error_code есть функция default_error_condition() и operator<<. В остальном они буквально одинаковые.

Если посмотреть что пишут в интернете, то вот тут автор оригинальной boost::system::error_code пишет

Functions should never return errors using error_condition, only with error_code. error_condition is only for comparing codes to.
Функции никогда не должны возвращать error_condition, только error_code. error_condition нужен только для сравнения кодов.

Также есть пара статей по написанию своих кодов для error_code и error_condition

Где говорится то же самое - функции должны возвращать error_code, а при проверке надо сравнивать их с error_condition.

На практике это должно выглядеть так:

// определяем список кодов ошибок
enum class MyErr { OK, PageNotFound, ServerNotFound, Timeout, InvalidArg, Crash, Unavailable };
// категорию ошибок и т.п.
struct MyErrCategory : std::error_category { ... };

// и используем это в функциях
std::error_code foo() {
  if (...) return MyErr::Timeout;
  return MyErr::OK;
}

Для проверки ошибок, мы можем просто сравнивать их с кодами выше

if (ec == MyErr::Timeout || ec == MyErr::Unavailable) Retry();
if (ec == MyErr::ServerNotFound || ec == MyErr::InvalidArg || ec == MyErr::Crash) Exit();
if (ec == MyErr::PageNotFound) CreateNewPage();

но тут видно, что у нас есть некоторые группы условий ошибок (буквально условий равенства), и мы можем сделать табличку

         | PageNotFound |ServerNotFound | Timeout | InvalidArg | Crash | Unavailable
TryAgain |              |               |    +    |            |       |      +
Fatal    |              |      +        |         |     +      |   +   |

Тогда мы можем сделать коды для этих групп условий равенства ошибок, еще одну error_category, и внутри категории задать таблицу преобразования кодов ошибки в код условия.

// определяем список кодов условий ошибок
enum class MyErrCond { OK, TryAgain, Fatal };
// категорию ошибок
struct MyErrCondCategory : std::error_category {
  ...
  bool equivalent(std::error_code ec, int cond) const noexcept {
    switch (static_cast<MyErrCond>(cond)) {
    case MyErrCond::TryAgain:
      return ec == MyErr::Timeout || ec == MyErr::Unavailable;
    case MyErrCond::Fatal:
      return ec == MyErr::ServerNotFound || ec == MyErr::InvalidArg || ec == MyErr::Crash;
    default: return false;
    }
  }
};

теперь мы можем упростить проверку ошибок:

if (ec == MyErrCond::TryAgain) Retry();
if (ec == MyErrCond::Fatal) Exit();
if (ec == MyErr::PageNotFound) CreateNewPage();

тут надо отметить, что PageNotFound и ServerNotFound не попадают в одно условие, и для PageNotFound можно оставить сравнение с кодом ошибки.

Вот именно это подразумевалось под "error_code для возврата ошибок, error_condition - для проверки ошибок".

Также не стоит возвращать error_condition, чтобы избежать ситуации, когда надо преобразовывать ошибки из разных источников.

error_code foo();
error_condition bar();

??? foo_bar() {
  error_code ec1 = foo();
  error_condition ec2 = bar();
  return ???;
}

Что касается того, почему в стандарте написано про переносимые ошибки - скорей всего, дело в том, что там определен стандартный enum class errc с кодами условий ошибок. И так как его значения зафиксированы в стандарте (почти, в стандарте не сказаны конкретные значения, но они начинаются с 1 и так далее), то это переносимый способ сравнивать системные ошибки. Более того, пользовательская функция может вернуть этот error_condition, чтобы, например, переслать его по сети на другую машину, где другие системные коды ошибок.
Но если вы определяете свои коды ошибок, они и так будут одинаковые на всех системах, и для своих ошибок точно не надо возвращать error_condition.

→ Ссылка