Возврат ошибки. std::optional
#опытным
У std::variant довольно громоздкий интерфейс при возврате ошибки вместе с результатом работы функции. Но в С++17 появился еще один класс, который имеет семантику "Или" для типов + более дружелюбный интерфейс.
Это std::optional. Этот шаблонный класс либо содержит нужный тип, либо не содержит его. Вот так может выглядеть код:
Для того, чтобы вернуть пустой optional, используется константа std::nullopt. А в остальном интерфейс очень похож на std::expected за исключением доступа к ошибке.
Но на мой взгляд, std::optional не очень подходит для обработки ошибок.
👉🏿 Он имеет семантику наличия или отсутствия значения. Отсутствие значения - это в принципе нормальная ситуация в программировании. Вы сделали Select к базе и получили пустоту, запросили что-то по апи и получили пустоту - вот самое место для std::optional.
Получается, что вы в одном месте кодовой базы используете опшинал для простой индикации наличия результата, а в другом случае отсутствие значения означает ошибку. Это несколько сбивает с толку. Хочется использовать разные инструменты для обоих этих случаев.
👉🏿 Если вам нужно специфицировать, какая конкретно ошибка произошла, то std::optional умывает руки. Нужно либо output параметры использовать, либо в принципе другой класс.
Если есть 23-й стандарт или доступ к бусту, то лучше использовать std::expected или boost::outcome.
Use the right tool. Stay cool.
#cpp17
#опытным
У std::variant довольно громоздкий интерфейс при возврате ошибки вместе с результатом работы функции. Но в С++17 появился еще один класс, который имеет семантику "Или" для типов + более дружелюбный интерфейс.
Это std::optional. Этот шаблонный класс либо содержит нужный тип, либо не содержит его. Вот так может выглядеть код:
struct Error {
std::string message;
};
std::optional<double> safe_divide(double a, double b) {
if (b == 0.0) { // здесь нужна нормальная проверка на равенство с epsilon
return std::nullopt;
}
return a / b;
}
auto div_result = safe_divide(10.0, 2.0);
if (div_result.has_value()) {
std::cout << "Result: " << div_result.value() << std::endl;
} else {
std::cout << "Error: there is no value" << std::endl;
}
// или с операторами
if (div_result) { // operator bool
std::cout << "Result: " << *div_result << std::endl; // operator*
} else {
std::cout << "Error: there is no value" << std::endl;
}Для того, чтобы вернуть пустой optional, используется константа std::nullopt. А в остальном интерфейс очень похож на std::expected за исключением доступа к ошибке.
Но на мой взгляд, std::optional не очень подходит для обработки ошибок.
👉🏿 Он имеет семантику наличия или отсутствия значения. Отсутствие значения - это в принципе нормальная ситуация в программировании. Вы сделали Select к базе и получили пустоту, запросили что-то по апи и получили пустоту - вот самое место для std::optional.
Получается, что вы в одном месте кодовой базы используете опшинал для простой индикации наличия результата, а в другом случае отсутствие значения означает ошибку. Это несколько сбивает с толку. Хочется использовать разные инструменты для обоих этих случаев.
👉🏿 Если вам нужно специфицировать, какая конкретно ошибка произошла, то std::optional умывает руки. Нужно либо output параметры использовать, либо в принципе другой класс.
Если есть 23-й стандарт или доступ к бусту, то лучше использовать std::expected или boost::outcome.
Use the right tool. Stay cool.
#cpp17
❤15🔥8👍6😁2👎1
Обработка ошибок Шердингера
#опытным
Мы уже поговорили о том, что есть 2 подхода к обработке ошибок - исключения и возврат кода ошибки(std::expected или output параметры).
И хоть стандартная библиотека насквозь пропитана исключениями, она все-таки иногда, очень редко предоставляет альтернативные варианты. Например std::from_chars или std::to_chars.
Интересно, что в библиотеке std::filesystem очень многие функции и методы имеют две перегрузки работы с данными: одна с исключениями, другая - без. Например:
std::filesystem завезли в стандарт относительно поздно, поэтому было время задуматься о людях, пишущих небросающий код.
Однако выше приведены "образцово показательные" перегрузки. Посмотрите вот на это:
Есть класс std::filesystem::directory_iterator и эти итераторы нужно уметь инкрементировать, чтобы двигаться по элементам директории. Так как сигнатура операторов в С++ не поддерживает лишние параметры, то для варианта с кодом ошибок приходится определять именованный метод.
Обратите внимание, что increment не объявлен как noexcept!
То есть используя increment, вы не можете гарантировать отсутствие исключений. Да, ошибки при работе с файловой системой ОС передаются в качестве кодов ошибок. Но тот же std::bad_alloc increment кинуть может.
По всей видимости, мотивация не выбрасывать исключения связана с тем, что вызывающие стороны, использующие версию с исключениями, часто замусорены локальными блоками
Дизайн странный и путает людей. Поэтому будьте аккуратны с std::filesystem, если реально хотите убрать исключения с глаз долой.
Don't be confused. Stay cool.
#cpp17
#опытным
Мы уже поговорили о том, что есть 2 подхода к обработке ошибок - исключения и возврат кода ошибки(std::expected или output параметры).
И хоть стандартная библиотека насквозь пропитана исключениями, она все-таки иногда, очень редко предоставляет альтернативные варианты. Например std::from_chars или std::to_chars.
Интересно, что в библиотеке std::filesystem очень многие функции и методы имеют две перегрузки работы с данными: одна с исключениями, другая - без. Например:
bool exists( const std::filesystem::path& p );
bool exists( const std::filesystem::path& p, std::error_code& ec ) noexcept;
// or
bool remove( const std::filesystem::path& p );
bool remove( const std::filesystem::path& p, std::error_code& ec ) noexcept;
std::filesystem завезли в стандарт относительно поздно, поэтому было время задуматься о людях, пишущих небросающий код.
Однако выше приведены "образцово показательные" перегрузки. Посмотрите вот на это:
directory_iterator& operator++();
directory_iterator& increment( std::error_code& ec );
Есть класс std::filesystem::directory_iterator и эти итераторы нужно уметь инкрементировать, чтобы двигаться по элементам директории. Так как сигнатура операторов в С++ не поддерживает лишние параметры, то для варианта с кодом ошибок приходится определять именованный метод.
Обратите внимание, что increment не объявлен как noexcept!
То есть используя increment, вы не можете гарантировать отсутствие исключений. Да, ошибки при работе с файловой системой ОС передаются в качестве кодов ошибок. Но тот же std::bad_alloc increment кинуть может.
По всей видимости, мотивация не выбрасывать исключения связана с тем, что вызывающие стороны, использующие версию с исключениями, часто замусорены локальными блоками
try/catch для обработки «рутинных» событий. Условно: при работе с файлами может оказаться, что у программы нет прав доступа для них. Это в целом нормальная ситуация в файловой системе, но в первой перегрузке эти ситуации репортятся через исключения, как исключительные ситуации. Дизайн странный и путает людей. Поэтому будьте аккуратны с std::filesystem, если реально хотите убрать исключения с глаз долой.
Don't be confused. Stay cool.
#cpp17
👍18❤10🔥6😁2