Чем 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 шт):
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
- https://akrzemi1.wordpress.com/2017/07/12/your-own-error-code/
- https://akrzemi1.wordpress.com/2017/08/12/your-own-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
.