Правильные исключения в C ++ - PullRequest
28 голосов
/ 27 апреля 2010

Я только учусь, как обрабатывать ошибки в моем коде C ++. Я написал этот пример, который ищет текстовый файл, который называется неким файлом, и, если он не найден, выдаст исключение.

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
int array[90];
try
{
   ifstream file;
   file.open("somefile.txt");
   if(!file.good())
    throw 56;
}
catch(int e)
{
    cout<<"Error number "<<e<<endl;
}
return 0;
}

Теперь у меня два вопроса. Сначала я хотел бы знать, правильно ли я использую исключения. Во-вторых, (при условии, что первое верно), в чем выгода их использования по сравнению с оператором If else?

Ответы [ 6 ]

23 голосов
/ 27 апреля 2010

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

Во-вторых, можно задаться вопросом, является ли неправильное имя файла достаточно неожиданным, чтобы квалифицироваться как веская причина для исключения вообще.

Что касается преимуществ по сравнению с утверждением if / else: есть пара. Во-первых, исключения позволяют отделить код, который работает с ошибками, поэтому основная идея и удобочитаемость кода не теряются в лабиринте обработки ошибок. Во-вторых, когда у вас есть несколько уровней кода между генерацией и перехватом исключения, код, который выдает исключение, может не знать, как его действительно нужно обрабатывать. Ваш код, например, использует std::cout для сообщения о проблеме, но большая часть такого кода будет сообщать об ошибках вместо std::cerr. Вы можете перейти с одного на другой без каких-либо изменений в коде, который пытался открыть файл (который может быть глубоко в библиотеке, и не имеет ни малейшего представления о том, что следует использовать для этого приложения - и может использоваться в приложении где оба неправильны, и MessageBox было предпочтительным).

8 голосов
/ 27 апреля 2010

Сначала я хотел бы знать, правильно ли я использую исключения.
Да, хотя обычно вы хотите, чтобы ваши исключения происходили от std :: exception.

Во-вторых, (если первое верно), в чем выгода их использования по сравнению с оператором If else?
Для приведенного примера ничего нет. Преимущество исключений приходит, когда у вас есть много глубоких вложенных функций, вроде этого.

#include <stdexcept>
#include <iostream>
#include <string>

void anErrorFunc(const std::string& x)
{
    ifstream file;
    file.open(x);
    if (!file)
        throw std::runtime_error("Could not open file");
}

void someOtherFunction(const std::string& y)
{
    //Do stuff
    anErrorFunc(y);
    //Do other stuff
}

int main()
{
    try {
        someOtherFunction("somefile.txt");
    } catch (std::exception &ex) {
        std::cout << "Ouch! That hurts, because: "
            << ex.what() << "!\n";
    }
}

Обратите внимание, что исключение будет зафиксировано в main() и someOtherFunction не нужно беспокоиться о прохождении кодов возврата ошибок.

3 голосов
/ 27 апреля 2010

Ну, вы правильно используете исключение в том, что в вашем коде нет ничего плохого. Тем не менее, как правило, мы не бросаем примитивные типы (даже если вы можете). Как правило, лучше создать объект, производный от std :: exception , и еще лучше создать объект std :: exception, который также является boost :: exception .

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

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

Вам также может быть интересно прочитать о исключениях и обработке ошибок из C ++ FAQ Lite .

2 голосов
/ 27 апреля 2010

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

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

Как следствие, вы не должны бросать и перехватывать исключение в одном и том же теле функции - просто делайте все, что вы хотите сделать вместо выброса.

1 голос
/ 27 апреля 2010

Синтаксически то, что вы делаете, правильно. Что касается стиля, то, как уже отмечали другие, вы должны генерировать что-то, произошедшее от std :: exception.

Что касается второй части вашего вопроса, я хотел бы подробнее остановиться на этом.

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

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

Традиционные способы справиться с этим - возвращать коды ошибок - имеют несколько проблем:

  1. Он загромождает код кодом обработки ошибок до такой степени, что фактическая логика запутывается.
  2. Он полагается на то, что программисты не ленятся и проверяют КАЖДЫЙ возврат кода ошибки, что часто является безрассудным предположением. (Программисты на C, будьте честны: когда вы в последний раз проверяли возвращаемое значение printf?)
  3. Добавляет накладные расходы на проверку и обработку ошибок к КАЖДОМУ вызову функции, независимо от того, есть ошибка или нет.

Исключения решают эти проблемы (с различной степенью успеха).

  • Исключения решают # 1, имея только связанный с исключениями код в точке обнаружения и в точке обработки. Промежуточные функции не загромождаются обработкой неясных ошибок, с которыми они сами не заинтересованы и не имеют возможности иметь дело.
  • Они решают # 2 путем принудительного обращения. Вы не можете игнорировать исключение. Вы должны принять меры по ним. (Ленивые программисты могут по-прежнему перехватывать все исключения, а затем игнорировать их, но здесь их ужасающая неспособность к программированию теперь выделена для всеобщего обозрения.)
  • Они решают проблему № 3 (если она не реализована наивно), имея почти нулевые затраты, когда они не используются, хотя часто при очень и очень высокой стоимости, когда они фактически используются.

Это не означает, что исключения являются конечной целью для обработки ошибок. Недостатки:

  1. Исключения обычно очень дороги при использовании. Они должны избегать, несмотря на их преимущества, если производительность имеет первостепенное значение.
  2. Исключения иногда приводят к очень непрозрачному коду. Это нелокальная передача управления - фактически немного более безопасные версии операторов goto, но для разных функций. Исключение может передавать управление из сотен уровней глубоко в вашем коде в исходных файлах, даже не имеющих никакого отношения к тем, над которыми вы работаете (и, фактически, вполне возможно, даже недоступных для вас). Этот вид «жуткого действия на расстоянии» может очень затруднить понимание кода.
  3. «Проверенные исключения» могут на самом деле быть хуже для генерации шума, чем обработка if старого стиля. Видите ли, они, как правило, более многословны, чем if или switch операторы, и тот факт, что вы должны обрабатывать проверенные исключения для кода даже для компиляции, делает их ответственными за многие ситуации.
  4. Из-за их высокой стоимости использования небрежное использование их для любой обработки ошибок может сделать ваш код медленным и раздутым.
1 голос
/ 27 апреля 2010

Синтаксически говоря, ваш код правильный. Идиотически говоря, может быть, не так много - или, по крайней мере, это зависит от контекста. Когда файл не может открыться, мы можем выполнить нашу обработку прямо внутри этого if( !file.good() ), если это совершенно обычное явление и возможно. Например, если пользователь просит открыть файл в текстовом редакторе, то вполне вероятно, что файл не существует. С другой стороны, если редактор не может найти файл правописания, это означает, что что-то (возможно) ужасно неправильно. Программа, вероятно, не была установлена, или пользователь возился с этим файлом - все возможно.

В C ++ мы используем исключения для исключительных случаев . То есть случаи, которые на самом деле не предназначены для того, чтобы происходить, и которые мы не «принимаем». Это в отличие от пользовательского файла, который не открывается, или неправильного пользовательского ввода, или отсутствия подключения к Интернету: все это примеры совершенно правильных событий, распространенных ситуаций, событий, которые мы ожидаем рано или поздно в ходе выполнения программы. Они не исключительные .

Теперь, каковы преимущества использования исключений по сравнению с условными выражениями? Позвольте мне распространить этот вопрос на любой другой механизм перехода (goto): возврат и условия. Исключения более выразительны, если вы хотите сказать следующее: если вы имеете дело с исключительным случаем, используйте исключения. Исключения также выполняются больше, чем обычные условия, аналогично тому, как виртуальные функции выполняются больше, чем условия. Правильный кодовый блок будет выполняться в зависимости от исключения, но правый scope будет обрабатывать исключение в зависимости от обработчиков.

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

Технически и на самом низком уровне исключения представляют собой сложный механизм прыжка. Еще в дни бабочки люди изобрели условное if как несколько сложное goto, чтобы повысить выразительность (потому что goto можно использовать для чего угодно) и уменьшить ошибки программиста , Циклические конструкции, такие как петля C for, также по сути представляют собой сложный прыжок с блестками и цветами радуги, опять же для уменьшения ошибок и повышения выразительности. C ++ представил свои новые операторы приведения по тем же причинам.

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

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