LINUX.ORG.RU

Правильное использование исключений

 ,


1

3

Я в учебных целях и из спортивного интереса разрабатываю класс для работы с bmp картинками. Функционал пока что примитивный - чтение из файла, запись в файл, вывод на в консоль (да, я упоролся), в дальнейшем будет еще что нибудь, например рисование примитивов на открытом изображении. О всяческих ошибках по моей задумке класс должен сообщать использующей его программе с помощью исключений. Например, если перед вызовом функции read() имя открываемого файла указано не было, выбрасывается исключение, или при чтении файл внезапно кончился, хотя должен был продолжаться, опять выбрасывается исключение.

Является ли частое использование хорошим тоном? Или может быть лучше вообще не использовать исключения, а просто сделать bool read(), который вернет false если случилась ошибка, и другие функции переписать в таком же духе? Какие еще особенности бывают при работе с исключениями?

★★★★

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

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

dib2 ★★★★★
()
Последнее исправление: dib2 (всего исправлений: 1)

Читай про exception safity. У Саттера того же.

Если не осилишь, то лучше вообще не использовать.

anonymous
()

http://www.e-reading.club/chapter.php/1002058/73/Mayers_-_Effektivnoe_ispolzo...

Рекомендую все книги Майерса прочитать. Использование исключений должно касаться только исключительных случаев - то есть, когда у тебя полный «ая-яй» будет. А бросать исключение по любому поводу, это плохо, я считаю. (Да и исключения «замедляют» программу, однако для учебных примеров это не так важно).

necromant ★★
()

По моему опыту ошибка = исключение - вполне хороший подход. И, кстати, никакие Майерсы этому не противоречат. Исключения не стОит использовать только если предполагается, что для функции генерация ошибки - обычный сценарий её использования. То есть, что она как-бы предназначена завершаться с ошибкой. В этом случае, конечно, возможно, что исключение - не лучший подход. А в случае твоего read() обработка ошибок с помощью исключений - правильный подход.

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

asaw ★★★★★
()

Является ли частое использование хорошим тоном? Или может быть лучше вообще не использовать исключения, а просто сделать bool read(), который вернет false если случилась ошибка, и другие функции переписать в таком же духе?

Исключения полезны когда ошибку надо через несколько уровней пробросить. Если у тебя функция, которая вызывает read знает, что делать, если он вернул ошибку, то тут коды возврата удобнее - хотя бы, чтобы не городить try/catch на каждый вызов. Если нет и обрабатывать будет кто-то выше по стеку, то удобнее исключения, чтобы не плодить лапшу if(!read()) return false.

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

Ну и, имхо, слишком много исключений - неправильно. Всё-таки выбор исключения - «исключительная ситуация».

DarkEld3r ★★★★★
()

Использовать исключения - хорошо, при соблюдении ряда условий.

  • исключение - от слова исключительный, и должно использоваться соответственно, т.е. только в действительно неожиданных ситуациях. Простой пример обратного - mybitmap::setpixel мог бы кидать исключение при попытке нарисовать вне границ изображения. Однако это вполне легитимная операция, например (условно) в игре изображение персонажа может пересекать границу экрана. Кидать исключение здесь - означает что либо персонаж не будет отрисован (точнее, отрисован частично, до первого пикселя вне экрана), либо пользователю придётся руками обрезать изображение персонажа пересекающего границу, либо функция отрисовки будет обрабатывать исключение на каждый внеэкранный пиксель. Первое не приемлемо по соображениям качества, второе - по соображениям удобства пользователя библиотеки, третье - по соображениям производительности. А всё потому что это никакая не исключительная ситуация. А вот ошибка сохранения или чтения файла - совсем другое дело.
  • обязательно наследоваться от std::exception и соблюдать его инварианты (throw() / noexcept what()). К слову, это адекватным образом возможно только начиная с C++11, где std::string::c_str noexcept - иначе можно будет хранить в исключении только тупые const char*.
  • внимательно следить за exception safety (хотя это нужно делать всегда, ибо bad_alloc может кинуть что угодно). Опять же c++11 в этом сильно помогает, ибо с ним можно вообще не использовать new и обычные указатели.
  • следить чтобы исключение не кинулось в деструкторе. Как правило это получается само собой, ибо освобождение ресурсов обычно не порождает ошибок, но бывают исключения. Например, гипотетический класс File закрывает файловый дескриптор в деструкторе, а close() может вернуть ошибку. В таких случаях обычно в деструкторе ошибку игнорируют, но делают дополнительный метод, который позволит ошибку таки обработать, если это нужно.

Например, если перед вызовом функции read() имя открываемого файла указано не было, выбрасывается исключение

Не представляю как можно спроектировать класс BMP файла так чтобы нельзя было указать имя файла. Либо это BMP::BMP(const std::string& filename), либо BMP::Read(const std::string& filename). Если же вы про проверку finename на "", то не нужно - не ваше дело что я хочу открыть. Неправильное имя файла обработает система, а лишние «умные» проверки порождают не кроссплатформенный код: откуда вы знаете что в моей систему пустая строка - не допустимое имя файла?

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

slovazap ★★★★★
()
Последнее исправление: slovazap (всего исправлений: 2)

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

Shadow1251
()

Спасибо всем, кто отписал, учту замечания.

WRG ★★★★
() автор топика

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

ck114
()

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

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

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

aureliano15 ★★
()
Ответ на: комментарий от ck114

Исключения следует использовать когда происходит ошибка «вне программы»

А что тогда ошибка «внутри программы»? std::bad_cast? Ну вы поняли...

asaw ★★★★★
()
Ответ на: комментарий от aureliano15

Недостатком исключений является их относительно низкая эффективность

Стоит отметить, что за них платишь даже, если всё идёт хорошо.

mix_mix ★★★★★
()
Ответ на: комментарий от intelfx

Например у вас есть объект Obj расположеный на стеке, в программа генерирует ексепшен и начинается процесс раскрутки стека. В этом процессе вызывается деструктор ~Obj в котором генерируется новое исключение - в принципе это UB которое по хорошему должно приводить к крашу (abort).

Второй пример, у вас Obj расположен в куче, вы выполняете delete obj, по сути это разворачивается в два вызова: obj->~Obj(); free( obj ); Если в obj->~Obj(); произайдет ексепшен будет мемори лик который никак не почистить.

zaz ★★★★
()
Ответ на: комментарий от zaz

Второй пример, у вас Obj расположен в куче, вы выполняете delete obj, по сути это разворачивается в два вызова: obj->~Obj(); free( obj ); Если в obj->~Obj(); произайдет ексепшен будет мемори лик который никак не почистить.

valgrind выражает своё согласие

rupert ★★★★★
()
Ответ на: комментарий от slovazap

А обязательно ли вообще создавать свой класс исключений, унаследованный от std::exception? Может можно просто кидать те же исключения, которые описаны в stl, например std::logic_error или std::runtime_error и их производные? Или создание своих исключений является хорошей практикой?

WRG ★★★★
() автор топика
Ответ на: комментарий от WRG

исключение — это объект и его можно использовать не только для вывода в лог what().

anonymous
()

Правильное использование исключений - не использовать их никогда

quest ★★★★
()
Ответ на: комментарий от WRG

Если ты не ставишь noexcept у метода, то компилятор сгенерирует дополнительный код для обслуживания этого механизма. Как результат - разбухание кода. В stl некоторым контейнерам и алгоритмам запрещено использовать move-семантику, если методы не помечены как noexcept. В итоге, например, вместо оператора перемещения будет использован оператор копирования, что даст сильную просадку в производительности. Поэтому в проектах, критичных к размерам кода и производительности тупо выпилвают поддержку исключений, а-ля -fno-exceptions в gcc.

Shadow1251
()
Ответ на: комментарий от WRG

в с++ исключением может быть все что угодно, даже примитивные типы

EugeneBas ★★
()
Ответ на: комментарий от WRG

Развёрнутая статья на эту тему есть на хабре. Я же от себя скажу, что это как раз и есть ловля блох, за редкими исключениями, когда речь идёт о задачах «критичных к размерам кода и производительности», как сказал Shadow1251 (это больше относится к программированию устройств и задачам жёстко реального времени). Вот исключения в деструкторе действительно опасны.

aureliano15 ★★
()
Ответ на: комментарий от WRG

Совершенно необязательно. Если существующий класс исключения как раз соответствует обрабатываемой ошибке, то разумнее использовать его. Если класс соответствует более общей ошибке, то можно создать производный от него класс. Если подходящих классов нет, то создать свой. Единственно, желательно всё-таки наследовать свой класс прямо или косвенно от exception, это позволит относительно корректно обрабатывать такие исключения в коде, написанном другими и ничего не знающим о твоих исключениях.

aureliano15 ★★
()
Ответ на: комментарий от WRG

Не обязательно, но да, хорошей практикой является. Через свои типы исключений можно передать дополнительные данные (например, путь к файлу который не удалось открыть) которые код обработки исключения сможет использовать. Также исключения выстроенные в иерархию удобнее обрабатывать:

} catch (My::CouldNotOpenImage& e) {
    std::cerr << "Файл " << e.GetFileName() << " не открылся, попробуем как-нибудь по-другому..." << std::endl;
    ...
} catch (std::exception& e) {
    std::cerr << "Ошибка: " << e.what() << std::endl;
}

если такое не нужно, на пустом месте городить иерархию конечно не стоит и std::runtime_error вполне хватит. logic_error, как я уже писал, мне кажется правильнее реализовывать assert'ами.

Я лично использую std::runtime_error в коде конечных приложений, свои классы исключений - в коде библиотек (тут лучше использовать своё исключение всегда чтобы ошибки от вашей библиотеки можно было отделить от остальных).

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

В данном примере вместо My::CouldNotOpenImage я бы лучше использовал стандартное исключение ios_base::failure по следующим причинам:

1. В методе what() можно вывести всё то же самое, что и в методе GetFileName().

2. Зачем изобретать велосипед?

3. Любой сразу поймёт, что это за исключение и как оно работает, что содержит, самопальное же исключение надо изучать.

4. Стандартное исключение сможет правильно обработать код, не знающий о существовании самопального (может в данном случае это и не актуально, но что будет в будущем?)

В общем же случае я бы всё-таки придерживался 2 правил:

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

2. Для вывода информации всегда использовал бы стандартную виртуальную функцию what(), а не собственные названия типа GetFileName() и т. п., т. к. код, не знающий о GetFileName(), скорее всего будет вызывать what().

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

В данном примере вместо My::CouldNotOpenImage я бы лучше использовал стандартное исключение ios_base::failure по следующим причинам

Вы ни разу не поняли о чём я хотел сказать.

GetFileName() не заменяет what(). В what() - текстовое описание, и оно у своего исключение никуда не девается, в GetFileName() же - конкретно имя файла. Из ios_base::failure имя файла не достать никак, и именно в этом смысл использования своих исключений - возможность передачи сериализованной информации об ошибке. Кроме того, изображение можно не открыть по причине битого формата, поэтому никакие производные std::system_error тут не подходят. Кроме того, std::ios_base::failure имеет чёткое предназначение:

The class std::ios_base::failure defines an exception object that is thrown on failure by the functions in the Input/Output library.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

изображение можно не открыть по причине битого формата, поэтому никакие производные std::system_error тут не подходят.

Если так, то согласен.

aureliano15 ★★
()
Ответ на: комментарий от slovazap

PS: но, опять же, тогда надо указать причину ошибки, например «неизвестный формат», а не просто имя файла, а для этого-таки лучше подходит функция what(), куда можно поместить полное сообщение и об имени файла, и о причине ошибки, и, возможно, что-то ещё. what() ещё хороша тем, что если когда-нибудь автор захочет создать на базе своего проекта библиотеку или просто присоединиться к более мощному проекту, где участвуют много программистов и могут вызывать его функции, они вряд ли будут разбираться со всеми этими GetFileName(), а просто вызывать метод what(). Хотя, конечно, определить в качестве бонуса помимо what() ещё и GetFileName(), CauseError() и т. д. тоже можно.

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

PS: но, опять же, тогда надо указать причину ошибки, например «неизвестный формат», а не просто имя файла, а для этого-таки лучше подходит функция what(), куда можно поместить полное сообщение и об имени файла, и о причине ошибки, и, возможно, что-то ещё. what() ещё хороша тем, что если когда-нибудь автор захочет создать на базе своего проекта библиотеку или просто присоединиться к более мощному проекту, где участвуют много программистов и могут вызывать его функции, они вряд ли будут разбираться со всеми этими GetFileName(), а просто вызывать метод what(). Хотя, конечно, определить в качестве бонуса помимо what() ещё и GetFileName(), CauseError() и т. д. тоже можно.

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

slovazap ★★★★★
()
Ответ на: комментарий от post-factum

Правильно использовать исключения в плюсах — это не использовать исключения.

Если исключения использовать в рамках отдельной закрытой подзадачи, с одним try/catch в точке входа без бросков наружу, то они здорово помогают писать простой код. Я сам предпочитаю обходится без них в 99.9% случаев, но иногда они бывают очень в тему.

anonymous
()
Ответ на: комментарий от slovazap

what() - это не более чем заглушка для отладки

Вот с этим принципиально не согласен. Парсить строку я, разумеется, не буду, просто выведу её на терминал/в мессэйдж-бокс/в лог/куда угодно ещё. Например, в чём разница между:

std::cerr << "Файл " << e.GetFileName() << " не открылся, попробуем как-нибудь по-другому..."

и

// конструктор исключения:
CouldNotOpenImage()
{
// ...
what_string = "Файл ";
what_string += GetFileName();
what_string += " не открылся, попробуем как-нибудь по-другому...";
// ...
}

// реализация what():
virtual std::string CouldNotOpenImage::what()
{
  return what_string;
}

// catch:
std::cerr << e.what() << std::endl;

Результат одинаков. Но во втором случае мы используем стандартный механизм, тогда как в первом изобретаем велосипед, что чревато несостыковками при коллективной работе. Имхо, всегда по возможности надо выбирать стандарт (или фактический стандарт), если на результат это не повлияет.

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

aureliano15 ★★
()
Ответ на: комментарий от mix_mix

Стоит отметить, что за них платишь даже, если всё идёт хорошо.

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

kawaii_neko ★★★★
()
Ответ на: комментарий от slovazap

И ещё о нестандартном коде

Вдогонку о нестандартном написании. Помню, как-то давно решил выпендриться на одном программистском форуме и написал конструкцию, увиденную ещё раньше в какой-то книге, типа

return a=7, b=8, a+b;

вместо

a=7;
b=8;
return a+b;

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

Нет, всегда надо стараться придерживаться стандарта, а оригинальничать тогда, когда это действительно даёт эффект, а не ради самой оригинальности.

aureliano15 ★★
()
Ответ на: комментарий от post-factum

Правильно использовать исключения в плюсах — это не использовать исключения.

Мне кажется, что это утверждение следует как-то аргументировать, иначе оно сильно теряет в весе и не вызывает доверия.

aureliano15 ★★
()

Является ли частое использование хорошим тоном?

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

Или может быть лучше вообще не использовать исключения, а просто сделать bool read(), который вернет false если случилась ошибка

Если использовать исключения, то чтобы обработать ошибку в контексте текущей задачи нужно будет постоянно заворачивать read в try/catch. Выглядит это ИМХО намного корявее чем if(!read()) {...}

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Если использовать исключения, то чтобы обработать ошибку в контексте текущей задачи нужно будет постоянно заворачивать read в try/catch. Выглядит это ИМХО намного корявее чем if(!read()) {...}

Сильно сомневаюсь.

Вот «не корявый» традиционный код:

// Что-то читаем:
if(!read())
{
  // Какой-то код обработки ошибки.
}
//...
// Читаем что-то другое:
if(!read())
{
  // И снова копируем тот же код
}
// ...
// Дальше километр кода с сотней дублирований обработки ошибки
// ...
// А где-то здесь мы устали и забыли поставить if:
read();
// Если в этом месте при чтении произошёл сбой,
// то мы об этом не узнаем и продолжим выполнение программы,
// как будто ничего не произошло.
// Естественно, результаты будут непредсказуемы.
// Причём проявиться ошибка может совсем в другом месте
// и найти её будет довольно трудно. Не говоря уже о том,
// что при отладке она может вообще не всплыть
// и попадёт в релиз.

А вот г-но-код с исключениями:

// Реализация read() с выбрасыванием исключения:
int read() throw
{
  // Какой-то код
  // ...
  // Исключение:
  if(error!=0)
    throw my_exeption();
}

int main()
{
  try
   {
     read();
     // ...
     read();
     // ...
     // И ещё много раз read() безо всяких if'ов.
     // ...
   }
  catch(my_exception& e)
   {
     // А здесь один единственный раз мы пишем наш код,
     // обрабатывающий ошибку.
   }
}

По-моему, преимущества второго подхода налицо:

1. Исходный код упростился и уменьшился.

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

3. Теперь очень трудно забыть обработать ошибку read() в каком-то месте и вызвать тем самым непредсказуемое поведение программы, причину которого будет довольно сложно выявить в реальном большом проекте. Даже особо одарённые личности, которые забудут поставить try(), не смогут это сделать, т. к. вызовется обработчик по умолчанию.

Так что try/catch мы используем по одному разу, в то время как при традиционном подходе действительно постоянно заворачиваем read() в if().

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

Исходный код упростился и уменьшился

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

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

Правильный аналог такой:

int main()
{
  try
   {
     read();
   }
  catch(my_exception& e)
   {
     // Обрабатываем ошибку
   }
  try
   {
     read();
   }
  catch(my_exception& e)
   {
     // Ещё раз
   }
}

Это нисколько не удобнее и выглядит как говно.

Ну а что касается if(!read()) то никто не запрещает обрабатывать ошибки толпой - read должен быть методом класса Reader, а значит можно просто фиксировать все случившиеся ошибки накапливая их в Vector<ReaderError>, далее делаем два метода Reader::hasErrors() и Reader::getErrors - и обрабатывай ошибки где хочешь.

Reader r(file);
...
r.read();
...
r.read();
...
if (r.hasErrors()) {
// обрабатываем
}
no-such-file ★★★★★
()
Ответ на: комментарий от Shadow1251

-fno-exceptions

Лучше вот это не включать, вроде можно сломать STL если они не смогут бросать исключения о нехватке памяти например, не?

vertexua ★★★★★
()
Ответ на: комментарий от vertexua

Если libstdc++ собрана без флага -fno-exceptions, а клиентский код с ним, то да, тот же new ( не new(nothrow) ) может бросить bad_alloc и поймать его не получится. Потому и пишут свои специализированные под задачу велосипеды.

Shadow1251
()
Ответ на: комментарий от eao197

Сударь, какого рода софт вы пишете на C++?

Что ты хочешь узнать этим вопросом? Рекомендую выучить русский язык, и попробовать спросить ещё раз.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Что ты хочешь узнать этим вопросом?

Хочу узнать, пишете ли вы вообще на C++? И если пишете, то что именно? Игры? Управление оборудованием? Околосистемный софт? Обработка изображения/звука? Реальное время? Что-то еще?

Или вы ограничиваетесь звиздежом на LOR-е?

eao197 ★★★★★
()
Ответ на: комментарий от no-such-file

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

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

А исключения считать фатальными ошибками, которые без помощи пользователя исправить нельзя (или вообще нельзя исправить), и обрабатывать в едином блоке catch (или в небольшом числе таких блоков).

Но даже в приведённом примере, где каждый вызов завёрнут в свой try/catch, что действительно выглядит несколько громоздко, мы всё-таки страхуемся от ошибки, когда в коде не обрабатывается та или иная ошибка - в этом случае программа просто завершится, напечатав строку, возвращаемую функцией what(), что намного безопаснее (и легче отладить), чем тихое игнорирование этой ошибки и продолжение работы, как будто ничего не произошло.

Что касается отложенной обработки ошибок, накапливаемых в векторе, - это тоже чаще всего неприемлемо. Если мы думаем, что прочитали данные в буфер, а на самом деле ничего не прочитали, то буфер содержит случайные (или предыдущие) данные. Если вслед за чтением сразу идёт обработка этих данных, то мы получим обработанные случайные числа и выведем на экран вместо картинки абракадабру, а потом будем гадать, почему так получилось и где мы испортили данные (скорее всего, прежде, чем догадаться, что мы просто не прочитали данные, будет потрачено масса времени сначала на проверку парсера, а затем на некорректное распределение памяти, что типично для C/C++ при недостаточно аккуратном программировании).

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

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

post-factum ★★★★★
()
Ответ на: комментарий от post-factum

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

А можно сказать иначе: при использовании кодов ошибок, возвращаемых традиционными C-функциями, начинается лапша с обработкой ошибок. При использовании разных библиотек одни функции ничего не возвращают, бросая исключения, другие же возвращают int, который нужно проверять, ошибка оно или нет. И от такой лапши совсем невесело становится. :-)

Так что это обоюдоострое утверждение. Естественно, исторически существуют разные подходы, и это создаёт проблемы. Причём проблемы возникают независимо от того, предпочитаем мы исключения или коды возвратов, если используются библиотеки, использующие разные подходы. В одном случае нам надо создавать обёртки, которые отлавливают исключения и возвращают в случае их возникновения коды ошибок. Во втором случае нам придётся создать обёртки, анализирующие код возврата и в случае ошибки бросающие исключение. Можно ещё обойтись вообще без обёрток, создав в коде эту самую лапшу. Из трёх подходов при программировании на C++ 2-й (с выбросом исключений вместо кодов ошибок) в большинстве случаев, имхо, самый простой и безопасный. А чтобы уберечься от этих проблем вообще, надо переходить на чистый C без плюсов (что в ряде случаев действительно оправдано, и далеко не только и не столько из-за исключений, сколько из-за наследования и шаблонов, упрощающих программирование, но часто усложняющих отладку). Но это уже совсем другой язык и другая парадигма программирования, не имеющая отношения к топику.

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

Вот с этим принципиально не согласен. Парсить строку я, разумеется, не буду, просто выведу её на терминал/в мессэйдж-бокс/в лог/куда угодно ещё.

Ещё раз: обрабатывающему коду нужна информация об ошибке в сериализованном виде. У вас ни на что кроме «вывести куда угодно» фантазии не хватает, в то время как в реальном приложении может понадобиться совершенно произвольная логика. Например, подгрузить so'шки обрабатывающие новые форматы файлов и попробовать загрузить тот же файл ещё раз. Или при невозможности открыть локальный файл, попробовать загрузить его из встроенного клипарта. Далее, даже если вам нужно просто вывести ошибку, what() ничем не поможет, потому что в реальных приложениях как минимум используется локализация, которая с вашими сгенерёнными строками ничего сделать не сможет.

Нет, всегда надо стараться придерживаться стандарта, а оригинальничать тогда, когда это действительно даёт эффект, а не ради самой оригинальности.

Нет такого стандарта по которому нужно ограничиваться what(). У вас просто весьма ограниченное понимание исключений.

slovazap ★★★★★
()
Ответ на: комментарий от no-such-file

Если использовать исключения, то чтобы обработать ошибку в контексте текущей задачи нужно будет постоянно заворачивать read в try/catch. Выглядит это ИМХО намного корявее чем if(!read()) {...}

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

slovazap ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.