Обработка ошибок в C ++ — недостаток использования std :: pair или std :: tuple для возврата кодов ошибок и возврата функций

Не вдаваясь в общие исключения и обсуждения кодов ошибок, как вы думаете, какие недостатки использования std::pair или же std:tuple для возврата нескольких значений, а именно возвращаемого значения функции И кода ошибки / успеха, аналогично тому, сколько Go разработчики, видимо, делают обработку ошибок?

Этот подход, очевидно, имеет то преимущество, что не нужно использовать параметры для возвращаемого значения функции или кода ошибки (в зависимости от того, какой способ вы предпочитаете).

7

Решение

«Что вы думаете о недостатках использования std :: pair или std: tuple для возврата нескольких значений, а именно возвращаемого значения функции И кода ошибки / успеха»

Основным недостатком этого упрощенного (уровня C) подхода к обработке отказов является

  • потеря безопасности.

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

Старый Бартон & Nackman Fallow Класс решил эту проблему безопасности, ограничив доступ к значению результата. По сути, вызывающий код должен проверить, есть ли является значение результата перед его использованием и использованием логически несуществующего значения результата вызывает исключение или завершение. boost::optional класс делает то же самое.

Если вы не хотите зависеть от Boost, тогда Optional Класс тривиально реализовать для типа результата POD, и за счет небольшой возможной неэффективности (динамическое распределение) вы можете просто использовать std::vector нести возможный результат без POD.

Задача состоит в том, чтобы сохранить ясность вызывающего кода, в чем весь смысл упражнения…

1

Другие решения

Эта «идиома» хороша, потому что и тип, и индикатор успеха являются возвращаемыми значениями функции. Неудача не может быть исключительной, поэтому исключения иногда неуместны.

Недостатком является то, что вы должны разделить два типа возврата. Это может быть ужасно; с помощью std::tie помогает, но вы не можете построить из множественного возврата.

bool success;
std::string value;
std::tie(success, value)=try_my_func();

Это довольно многословно.

Во-вторых, если один из типов является «необязательным» в зависимости от значения другого элемента в кортеже, он все равно должен быть сконструирован, что для некоторых типов все еще очень расточительно.

Если вы часто используете эту идиому, рассмотрите возможность использования чего-то вроде boost::optional тип. Это близко к хаскелю, может быть, чем многократный возврат го.

Ссылка

http://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html

2

Для этой цели в большинстве случаев я использую собственный тип оболочки, который вводит некоторый синтаксический сахар. Давайте посмотрим на пример:

template <class T>
struct Result
{
public:
enum Status {
Success,
Error
};

// Feel free to change the default behavior... I use implicit
// constructors for type T for syntactic sugar in return statements.
Result(T resultValue) : s(Success), v(resultValue) {}
explicit Result(Status status, std::string errMsg = std::string()) : s(status), v(), errMsg(errMsg) {}
Result() : s(Error), v() {} // Error without message

// Explicit error with message
static Result error(std::string errMsg) { return Result(Error, errMsg); }

// Implicit conversion to type T
operator T() const { return v; }
// Explicit conversion to type T
T value() const { return v; }

Status status() const { return s; }
bool isError() const { return s == Error; }
bool isSuccessful() const { return s == Success; }
std::string errorMessage() const { return errMsg; }

private:
T v;
Status s;

// if you want to provide error messages:
std::string errMsg;
};

Затем просто используйте этот класс в качестве возвращаемого значения в ваших методах, которые могут возвращать ошибки:

Result<int> fac(int n) {
if(n < 0)
return Result<int>::error("n has to be greater or equal zero!");
if(n == 0)
return 1;
if(n > 0)
return n * fac(n-1);  // gets automatically converted to int
}

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

Пример использования:

int main() {
for(int i = -3; i < 4; ++i)
{
Result<int> r = fac(i);
std::cout << i << " | ";
std::cout << (r.isSuccessful() ? "ok" : "error") << " | ";
if(r.isSuccessful())
std::cout << r.value();
else
std::cout << r.errorMessage();
std::cout << std::endl;
}
}

Выход:

-3 | error | n has to be greater or equal zero!
-2 | error | n has to be greater or equal zero!
-1 | error | n has to be greater or equal zero!
0 | ok | 1
1 | ok | 1
2 | ok | 2
3 | ok | 6

Одним из больших преимуществ пользовательского типа является то, что вы можете вставить некоторый элемент управления, гарантирующий, что клиентский код всегда проверяет наличие ошибок перед доступом к фактическому значению и получает доступ только к значению, если оно прошло успешно, соответственно к сообщению об ошибке, если это не так. Для этого мы можем расширить класс следующим образом:

struct Result
{
public:
// in all constructors, add:
Result(...) : ..., checked(false) {...}

// in the error checker methods, add: (and drop const-ness)
bool is...() { checked = true; return ... }

// rewrite the value conversion as follows:
operator T() const { std::assert(checked && isSuccessful()); return v; }
T value() const    { std::assert(checked && isSuccessful()); return v; }

// rewrite the errorMessage-getter as follows:
std::string errorMessage() const { std::assert(checked && isError()); return errMsg; }

private:
...
bool checked;
};

Возможно, вы захотите сделать определение класса в зависимости от режима сборки (отладка сборки / выпуск сборки).

Обратите внимание, что пример должен быть переписан следующим образом:

Result<int> fac(int n) {
if(n < 0)
return Result<int>::error("n has to be greater or equal zero!");
if(n == 0)
return 1;
if(n > 0) {
Result<int> r = fac(n - 1);
if(r.isError()) return r;  // propagate error (similar to exceptions)
return n * r;              // r gets automatically converted to int
}
}

Основной код, указанный выше, все еще действителен, так как он уже проверял ошибки перед доступом к значению / сообщению об ошибке.

2

Это лучше, чем обычные коды ошибок, потому что вам не нужно тратить свою жизнь без параметров. Но он все еще сохраняет все очень серьезные недостатки.

На самом деле, это всего лишь микросмена, это все еще коды ошибок — никакой существенной выгоды.

0
По вопросам рекламы [email protected]