#новичкам
Если у вас нет std::variant, std::expected, std::optional, вам лень засовывать ошибки в объекты, то у вас остается не так много вариантов. Один из них - output параметры функции.
Идея донельзя простая. Не хочешь использовать сложные типы? Добавь дополнительный параметр функции!
Но конфигурация возвращаемого значения и параметров функции может быть разная:
👉🏿 С-style. Возвращаем int, который каким-то образом кодирует ошибку + потенциально какую-то полезную информацию, а результат работы функции записывается в выходной параметр. Например системный вызов read:
ssize_t read(size_t count, int fd, void buf[count], size_t count);
Возвращает количество прочитанных байт, либо -1, если произошла ошибка. Конкретная ошибка передается через errno. Сами данные записываются в buf.
Так как это не С++ подход, то он содержит все недостатки отсутствия ООП.
👉🏿 Код результата 1. Возвращаем enum успешности операции, а сам результат возвращаем в одном или нескольких выходных параметрах.
enum Code {
Success,
DivisionByZero,
NegativeNumber,
Overflow
};
Code safe_sqrt(double x, double& result) {
if (x < 0) {
return Code::NegativeNumber;
}
result = std::sqrt(x);
return Code::Success;
}
double result = 0.;
auto code = safe_sqrt(-4.0, result);
if (code != Code::Success) {
process_error(code);
} else {
std::cout << "Result: " << result << std::endl;
}В этом случае приходится объявлять переменную результата заранее.
👉🏿 Код результата 2. Наоборот, возвращаем результат, а в выходные параметры передаем потенциальную ошибку:
double safe_sqrt(double x, Code& code) {
if (x < 0) {
code = Code::NegativeNumber;
return {};
}
code = Code::Success;
return std::sqrt(x);
}
Code code = Code::Success;
auto result = safe_sqrt(-4.0, code);
if (code != Code::Success) {
process_error(code);
} else {
std::cout << "Result: " << result << std::endl;
}Такая сигнатура никак не форсит человека проверить код ошибки, можно случайно забыть проверить воспользоваться невалидным результатом.
Этот подход используется в том числе в стандартной библиотеке:
bool exists( const std::filesystem::path& p, std::error_code& ec ) noexcept;
Теперь уже заранее надо код результата инициализировать.
👉🏿 Универсальный. Можно ввести и универсальный кодстайл: возвращаем bool в качестве индикатора успешности выполнения функции, а результаты и ошибки возвращаем в output параметрах.
bool parse_coordinates(const std::string& input,
double& x, double& y, double& z,
std::string& error_message);
double x, y, z;
std::string error_msg;
if (parse_coordinates("10.5,20.3,30.7", x, y, z, error_msg)) {
std::cout << "Coordinates: " << x << ", " << y << ", " << z << std::endl;
} else {
std::cout << "Error: " << error_msg << std::endl;
}
Здесь сигнатура явно форсит проверить результат(особенно с nodiscard). Но еще больше данных нужно объявлять до вызова функции и код становится все менее декларативным.
Наверное когда-то это был популярный подход к обработке ошибок. Но в современном С++ существуют хорошие альтернативы, которые сделают ваш код проще, понятнее и консистентнее.
Prevent misuse. Stay cool.
#design