LINUX.ORG.RU

C++, exceptions


0

0

Как лучше применять исключения в программах использующих ООП на C++ (и стоит ли применять их вообще)?

Сейчас пытаюсь написать маленькую программку с использованием исключений, но пока получается как-то кривовато...

Вот например есть три клсса: Foo, Boo и Bar, которые соответственно могут выбрасывать исключения EFoo, EBoo =) и EBar (и наследованные от них). Причём экземпляр класса Bar используется и внутри Foo и внутри Boo. Что делать когда Bar выкидывает исключение EBar? Foo и Boo должны свободно пропускать его вниз по стеку вызовов или же перехватывать и как-то отображать на EFoo и EBoo? В первом случае (пропускаем) появляется проблема - как вызывающая функция определит, из какого класса выпало исключение - Foo или Boo? Во втором случае (вылавливаем и выкидывам своё исключение) появляется сразу две проблемы - 1) куча лишних перехватов исключений, ведь кроме EBar у нас есть std::exception и возможно какие-то другие исключения других классов библиотек, которые тоже придётся перехватывать; 2) как именно отображать EBar на EFoo/EBoo чтобы сохранилась вся или хотябы основная часть исходной информации: хранить копию EBar в новом исключении, сделать новую дочернюю иерархию от EFoo/EBoo которая бы отражала смысл ошибок EBar или как-то ещё? Можно вообще отказаться от исключений и перейти на return-коды. Но и тут появляется куча проблем: 1) иногда функции при нормальной работе могут вернуть почти всё что угодно и для детализации ошибки придётся городить что-то типа errno/GetLastErrorCode; 2) неявное игнорирование ошибок, что тоже не сильно хорошо; 3) если нужно тщательно проверять ошибки после каждого вызова функции (в каких-нибудь сильно важных учатсках кода например), то код вообще распухает до ниипических размеров, причём в отличие от исключений, где код обработки ошибок явно отделяется от нормального кода, тут он будет равномерно размазан по всему телу функции; 4) сложно группировать сходные по смыслу ошибки (например ошибки ФС отдельно, сеть - отдельно), с исключениями это делается с помощью наследования и можно в catch удобно отделять мух от котлет.

Интересует мнение опытных программистов по этому поводу, буду рад полезным ссылкам и литературе по теме. Также хочется посмотреть исходники каких-нибудь opensource проектов на C++/OOP с использованием исключений и других способов обработки ошибок (подскажите названия, а то я потыкался в гугль, но ничего толкового не нашёл =().

Вопросы производительности меня не интересуют (по крайней мере сейчас).

З.Ы. Высеры типа "C++ говно, ObjectXLAM рулит" будут проигнорированы, так что не тратьте пожалуйста на это своё и моё время =).

Deleted

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

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

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

Но как тогда быть с этой проблемой?

>> В первом случае (пропускаем) появляется проблема - как вызывающая функция определит, из какого класса выпало исключение - Foo или Boo?

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

Есть некие классы потоков, например Socket и StdIn. Есть так же класс Select, который внутри использует select для ожидания ввода с клавиатуры и получения данных на сокет. В нём регистрируются экземпляры обоих классов потоков и он вызывает функцию handle() каждого класса если есть ввод. И вот при вызове select_instance.doSelect() изнутри одного из классво потоков выпадывает исключение. Причём не ESocket и EStdIn, а std::bad_alloc какой нибудь. Как определить откуда именно оно выпало?

Deleted
()

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

Если ошибка может быть обработана на месте, по возможности используй код ошибки.

Если надо тащить ошибку наверх, генери исключение, если не напрягает геморрой с динамической памятью.

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

>> Если ошибка может быть обработана на месте, по возможности используй код ошибки.

Как так? Если она может быть обработана на месте, то мы её и обрабатываем на месте, иначе выкидываем исключение. Куда здесь прикрутить код ошибки?

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

> В первом случае (пропускаем) появляется проблема - как вызывающая функция определит, из какого класса выпало исключение - Foo или Boo?

> И как вообще вызывающая сторона должна определить, какая цепочка вызовов привела к ошибке

А зачем? Если речь идет об отладке, то в компиляторах обычно есть (непереносимые) средства определения точного места возникновения исключения, то же и с отладчиками. Если нет - то как и зачем ты хочешь использовать информацию о цепочке вызовов?

> Есть некие классы потоков, например Socket и StdIn. [...] выпадывает исключение. Причём не ESocket и EStdIn, а std::bad_alloc какой нибудь.

По-моему, ты смешал несколько вопросов в один - тебя интересует, как получить стектрейс для анализа человеком или как организовать обработку исключений в программе, без вмешательства человека?

tailgunner ★★★★★
()

>Также хочется посмотреть исходники каких-нибудь opensource проектов на C++/OOP с использованием исключений и других способов обработки ошибок (подскажите названия, а то я потыкался в гугль, но ничего толкового не нашёл

Посмотрите на программы использующие Qt/KDE, они почти наверняка используют исключения (не все конечно, но найти таки там вероятность увеличивается)

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

>> Если речь идет об отладке, то в компиляторах обычно есть (непереносимые) средства определения точного места возникновения исключения, то же и с отладчиками. Если нет - то как и зачем ты хочешь использовать информацию о цепочке вызовов?

Я не конкретно о цепочке вызовов а о проблеме пример которой привёл дальше (socket+stdin+select). Просто неправильно сказал =).

>> По-моему, ты смешал несколько вопросов в один - тебя интересует, как получить стектрейс для анализа человеком или как организовать обработку исключений в программе, без вмешательства человека?

Меня интересует второе (как организовать обработку исключений в программе, без вмешательства человека). В данном случае например мы определяем (как?) конкретно в каком экземпляре какого класса выпало исключение (socket || stdin) и после этого пересоздаём класс, или например делаем реконнект сокета...

Deleted
()
Ответ на: комментарий от tailgunner

>> И как вообще вызывающая сторона должна определить, какая цепочка вызовов привела к ошибке

> А зачем? Если речь идет об отладке, то в компиляторах обычно есть (непереносимые) средства определения точного места возникновения исключения, то же и с отладчиками. Если нет - то как и зачем ты хочешь использовать информацию о цепочке вызовов?

Например, при пересылке файле в сети и при отображении на экран генерится FileNotFound и хочется напечатать: "Пересылка не удалась: файл не найден" и "Не удалось отобразить файл: файл не найден"

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

>Я не конкретно о цепочке вызовов а о проблеме пример которой привёл дальше (socket+stdin+select).

ИМХО, если bad_alloc, то нужно обрабатывать на месте.

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

В Qt исключения не используются, в KDE судя по всему тоже, по наследству =).

Deleted
()
Ответ на: комментарий от generatorglukoff

> Посмотрите на программы использующие Qt/KDE, они почти наверняка используют исключения (не все конечно, но найти таки там вероятность увеличивается)

libtorrent использует исключения хардкорно -- как регулярный метод передачи сообщений

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

>> ИМХО, если bad_alloc, то нужно обрабатывать на месте.

bad_alloc только для примера.

Deleted
()
Ответ на: комментарий от dilmah

>> libtorrent использует исключения хардкорно -- как регулярный метод передачи сообщений

Спасибо, сейчас заценю...

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

> В данном случае например мы определяем (как?) конкретно в каком экземпляре какого класса выпало исключение (socket || stdin) и после этого пересоздаём класс, или например делаем реконнект сокета...

boost::asio посмотри

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

libtorrent, afaik, тоже asio использует

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

> Меня интересует второе (как организовать обработку исключений в программе, без вмешательства человека). 

Тогда нужно придумать нормальную иерархию исключений. Например:

std::runtime_error
    yourns::SelectLoopError
          yourns::SocketError
          yourns::FileStreamError

> В данном случае например мы определяем (как?) конкретно в каком экземпляре какого класса выпало исключение (socket || stdin) 

Ну, если тебе это в самом деле нужно, сделай в select_loop_error член-указатель на объект типа Select.

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

>> libtorrent использует исключения хардкорно -- как регулярный метод передачи сообщений

> Спасибо, сейчас заценю...

ИМХО, это была мрачная шутка. Исключения - это для исключительных ситуаций aka ошибок.

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

> Например, при пересылке файле в сети и при отображении на экран генерится FileNotFound и хочется напечатать: "Пересылка не удалась: файл не найден" и "Не удалось отобразить файл: файл не найден"

Всё просто: кто рисует пишет "Не удалось...", а потом прицепляет сообщение от исключения...

yz
()

ситауация несколько искуственная. иерархия исключений должна проектировать на основании информации о возможных ошибках/исключительных ситуациях системы - а не на основе существующей иерархии классов. т.е. у вас будет две совершенно различных иерархии, и пересечение их будет на блоках try/catch. в qt исключения не используются, там для тех же целей используются сигналы/слоты. касательно альтернативных способов советую посмотреть на boost.optional - даёт возможность корректно сигнализировать об ошибке с помощью возвращаемого значения; имеет смысл использовать оба механизма. для понимания разницы между ошибками и исключительными ситуациями советую книгу "Камерон Хьюз, Трейси Хьюз : Параллельное и распределенное программирование с использованием С++", там этому вопросу уделена целая глава

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

>ИМХО, это была мрачная шутка. Исключения - это для исключительных ситуаций aka ошибок

вообще-то это разные вещи

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

>> ИМХО, это была мрачная шутка. Исключения - это для исключительных ситуаций aka ошибок

> вообще-то это разные вещи

Это хорошее приближение.

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

>> Это хорошее приближение

> и распространённая ошибка проектирования

Можно сказать, общепринятая. Но есть, есть люди, которые не попались в сети общепринятого^Wбыдлоздравого смысла ;)

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