Вопрос новичка в C ++ - базовая обработка ошибок с использованием try, throw, catch - PullRequest
5 голосов
/ 25 января 2011

Я пытаюсь понять обработку ошибок в C ++.

Я читал, что использование try, throw, catch является лучшим стилем и менее сложным, чем использование операторов if с возвращаемыми значениями. Но я не уверен, что я действительно понимаю, как пробовать, бросать, ловить работает. Я сделал простой пример ниже, и было бы здорово получить отзыв о любых проблемах или плохом стиле. Моя цель - сделать функцию из примера, которая проверяет результаты другого расчета.

Вот вопросы, которые у меня есть: попробуй, брось, поймай: (1) Должен ли оператор catch быть включен в мою функцию? Или это должно быть где-то еще, как в main () или в функции, где выполняется первоначальный расчет?

(2) Излишне ли пытаться, ловить, бросать что-то такое простое (я бы хотел улучшить свой стиль)?

(3) Если произошла ошибка, я бы хотел прервать программу. Как бы я это сделал? Или «поймать» означает, что это делается автоматически?

(4) Я не понимаю использование cerr. Почему бы просто не использовать кут? Правильно ли я использовал cerr здесь? Должен ли я также использовать его в операторах if / else?

Большое спасибо за любую помощь.

Вот пример, который я сделал:

double calculated = 10.2; // from previous calculation
double tolerance = 0.3; // I can set this in this function
double valueWanted = 10.0; // from previous calculation

const int calcError = 5; // I picked this number randomly to use for indicating an error

try
{
   if (fabs(fTargetValue - fCalculated) <= fTolerance)
     cout << "Result is within range.";

   else
     cout << "Failed.";
     throw calcError;
}

catch (const int calcError)
{
   cerr << "The calculation failed.\n" << endl;
}

Ответы [ 5 ]

4 голосов
/ 25 января 2011

Ну, это много вопросов. Я постараюсь дать вам несколько советов:

(1) Не включайте try-catch в свою функцию. Бросок исключения делается для того, чтобы сообщить внешнему миру, что что-то произошло. Если вы можете справиться с проблемой в своей функции, не бросайте ее вообще ^^ Хорошая обработка ошибок, как правило, заключается в том, чтобы перехватить ошибку как можно скорее (в вызывающей программе) или в обработчике общего назначения, как в main, для обработки изящно необработанных ошибок. .

(2) Как правило, используйте исключение для ... исключительных вещей. Ошибки являются хорошим кандидатом на исключительные вещи. Исключение может быть выдано для таких вещей, как переполнение или деление на ноль в математической библиотеке. Вы должны решить, но в целом это хорошо для обработки ошибок с исключениями.

(3) catch не означает, что ваша программа закончится. На самом деле это наоборот. Улавливая исключение, вы говорите, что справитесь с проблемой ^^ Если вы хотите завершить работу, простой способ в простой программе - не перехватывать исключение, поскольку поведение по умолчанию для неперехваченного исключения - завершение программы ^^ Вместо этого вы можете явно прервать вашу программу в некотором блоке catch.

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

my2c

2 голосов
/ 25 января 2011

Стандарт C ++ имеет несколько классов исключений, которые все являются производными от. Я предлагаю вам сделать это вместо того, чтобы бросать и ловить POD. Это тоже не сложно и улучшит (и укажет тип ошибки) примерно так

class CalculationError : std::invalid_argument
{
public:
    CalculationError(std::string const& msg)
        : std::invalid_argument(msg)
    {}
};

Для быстрого обзора иерархии исключений перейдите на http://www.richelbilderbeek.nl/CppExceptionHierarchy.htm

Проблема в том, что когда вы выбрасываете тип POD, прикрепленное сообщение отсутствует. Большая часть создания исключений - это возможность написать сообщение о том, что могло пойти не так, и как это исправить. Это невозможно при броске int.

В C ++ есть три потока вывода: log, cerr и cout. Каждый из них представлен по-разному, что означает, что при запуске вашей программы вы можете использовать командную строку для фильтрации каждого из этих потоков. Это отлично подходит для отладки, так как вы можете отфильтровать по cerr и посмотреть, не прошла ли ваша программа тест.

Пример: my_program > out.txt 2> log.txt (cout идет в out.txt, остальные в log.txt)

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

Также важно отметить, что, будучи пойманным, исключение «проглатывается». Если вы не можете или не хотите иметь дело с ошибкой здесь, лучше написать

catch(/* Error to be caught */)
{
    throw; // Rethrows original exception, propagating it upwards
}

Если вам нужна хорошая литература по этому поводу, Херб Саттер написал книгу под названием Exceptional C++, и она охватывает безопасность исключений практическим и просветительским способом (imo). Определенно стоит проверить, хотите ли вы знать, когда и почему вам нужно выдавать исключения.

Надеюсь, это поможет!

2 голосов
/ 25 января 2011
  1. Оператор catch должен быть в первой функции выше той, которая выдает (может быть, в функции, которая выдает), которая может восстановиться после исключения и позволить программе продолжить нормально.

  2. Да, на самом деле нет смысла бросать, если вы ожидаете его поймать. Кроме того, ваш обычный программный поток не должен выдавать. Как правило, бросать нужно только тогда, когда вы попадаете в ситуацию, которую вы не ожидаете, когда-либо случиться. Исключения называются исключениями, потому что они случаются в исключительных обстоятельствах. Часто хорошим временем для использования исключений является взаимодействие с программной средой. Вы обычно ожидаете, что определенные вещи будут работать, например, чтобы иметь возможность распределять память, открывать файл, получать полный пакет данных от сетевого устройства и т. Д. Все эти случаи должны приводить к возникновению исключения. Кроме того, если ваша программа получает входные данные, она должна сначала проверить ее. Но позже, во время обработки, если что-то не так с данными, которые уже должны были быть отклонены проверкой, например деление на ноль, возникающее из-за странных входных данных, это также будет исключительной ситуацией. Если вы слишком сильно полагаетесь на исключения, когда ожидаемые события случаются, процесс и логику вашей программы могут оказаться слишком сложными, а обслуживание программы становится излишне трудным.

  3. Если есть ошибка, просто не ловите. Если вы не поймете, исключение перейдет полностью к вашей основной функции, а затем перейдет во время выполнения, которое завершит вашу программу. И для некоторых O.S.s, таких как windows, это приведет к созданию файла мини-дампов, который вы можете использовать для отладки вашей программы, чтобы выяснить, из-за какого исключения она была прервана.

  4. cerr и cout дают вам два способа вывода информации из вашей программы. В тех случаях, когда другая программа использует выходные данные вашей программы для выполнения чего-либо, она будет читать cout и ожидать, что поймет это. Это означает, что если вы хотите записать ошибки или предупредить, что программа-потребитель не поймет, вы должны записать их в cerr, чтобы не путать вторую программу, которая читает ваши программы, с обычным выводом cout.

2 голосов
/ 25 января 2011

Хорошо, во-первых, ваш пример будет выдаваться каждый раз, потому что у вас нет скобок для области видимости после else. Поэтому будет выполняться только cout << "Failed."; и throw calcError каждый раз, независимо от того, был ли результат в диапазоне или нет. Измените это на:

else
{
    cout << "Failed.";
    throw calcError;
}

В случае, если выброшено исключение, код начнется в пределах блока catch, который вы определили, сообщив, что расчет не выполнен.

Если результат находится в диапазоне (throw никогда не вызывается), код начнет выполняться непосредственно после вашего блока catch.

Когда вы бросаете тип, этот тип поступает в обработчик перехвата. Это позволяет вам определять обработчики уловов для разных типов. В этом случае вы бросаете (и ловите) const int. Это все хорошо. Как правило, мы бросаем std::exception или производную от этого. Ваши собственные классы исключений могут содержать информацию, относящуюся к ошибке. В вашем случае вы могли бы включить простое сообщение о том, что оно выходит за пределы допустимого диапазона, или даже включить const int, который потерпел неудачу.

1 голос
/ 25 января 2011

Разве вы не забыли блок вокруг остального случая

try
{
   if (fabs(fTargetValue - fCalculated) <= fTolerance)
     cout << "Result is within range.";

   else {
     cout << "Failed.";
     throw calcError;
   }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...