Pull to refresh

Comments 44

В новом стандарте C++ убрали спецификации исключений в объявлениях методов и оставили только noexcept вместо throw().
Буду рад поработать с полноценно поддерживающим новый стандарт компилятором. А пока лучше писать. А так же у нас используется старый компилятор GCC для Linux билдов, который скажет тебе много ласковых слов если не напишешь throw().
Под виндой тот же MSVC не поддерживает спецификации исключений, позволяя указывать лишь throw() и то, только для деструкторов.
Мы работаем с Visual Studio 2008, там все работает нормально. Написание throw() в классах исключений для всех методов у нас обязательно.
Да, my fault. Пустой throw() работает для всех методов. Что в общем-то равноценно тому же noexcept из нового стандарта, потому что инструкцию типа throw(std::runtime_error) студия всё равно заигнорит.
Он игнорируется в VS. Так что от того, что вы его пишете ничего не меняется. И вообще deprectade фича, которая непонятно для чего задумывалась.
Задумывалась то она правильно, вот только оказалась несовместимой с шаблонами.
Ну не знаю, что в ней правильного? Если бы это было на этапе компиляции, тогда — да. А в рантайме есть ли разница через какую функцию Ваша программа упадет?
Идея в том, что имея заголовок некой библиотечной функции, хотелось бы знать, какие исключения она может выбросить. Вы же знаете, например, коды ошибок, возращаемых файловыми операциями, и можете сказать юзеру «файл не найден» или «отказано в доступе». И юзер может после этого что-то поправить и таки заставить программу работать.
Вот и с исключениями хотелось бы так же.
Угу. только документация имеет привычку отставать от актуальной версии кода, а неперехваченное исключение может закончится terminate() на ровном месте. В ттеории, компилятор мог бы автоматически проверить спецификацию исключений.
Все-таки создание внутри обработчика объекта класса визитор мне ну очень не нравится. «Шестое чувство» просто-таки вопит, что его со стека надо убирать. Что, если кто-то проанализирует исключение и решит передать его дальше? Что, если возникнет исключение в самом визиторе? Слишком много вопросов, не имеющих однозначного ответа.
Так как отказаться от виртуальных функций мы не можем, возможно, стоит копнуть в сторону создания собственного менеджера памяти для визиторов, или вместо конструктора использовать фабрику, возвращающую ссылки…
Что, если кто-то проанализирует исключение и решит передать его дальше?

Что именно вас беспокоит? Если объект создавать на стеке он корректно уничтожится как только выйдет за область видимости catch'а.

Что, если возникнет исключение в самом визиторе?

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

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

Добавлю свои пять копеек, касательно Windows специфики. В проектах на WinAPI как правило есть файл ресурсов *.rc, и там помимо всяких шаблонов диалогов, определения иконок и прочего добра, есть ещё определения строк вида «идентификатор ресурса (строки) -> значение» (сами идентификаторы числовые, традиционно определяются в resource.h). Есть специальный компилятор ресурсов. Потом всё это добро окажется в специальной секции ресурсов EXE или DLL файла. Одна из распространённых ошибок новичка — пихать строчки сообщений, показываемых пользователю, прямо в код. Не рекомендуется (мягко говоря).

Но и злоупотреблять этим правилом не стоит, имхо. Не все сообщения из исключений предназначены для пользователя. Последнее время придерживаюсь такой тактики: если модуль реализует UI, то там да, все строки надо хранить в ресурсах. Если же модуль без UI, то там можно разместить строки в заголовочных файлах, определив через #define или как константы (в т.ч. как мемберы соответствующих классов). Это должна быть техническая информация для разработчиков. На английском. Переводить её для пользователя никто не будет (это одна из причин, почему вообще существуют ресурсы — они локализуются, не только строки, по идее и шаблоны диалогов с иконками и прочими картинками можно заточить под страну/язык пользователя).

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

По поводу GUI не gui, у нас два режима работы: gui и консольный режим. И на данный момент, мы стараемся везде использовать ресурсы. Исключения только для log файлов.
Да это так, больше для новичков, ни разу не слышавших слово «ресурсы».

Вообще же, для меня что GUI, что CUI (консольный UI) — всё одно UI. Visual Studio со мной согласна и генерит *.rc для консольных проектов также, как и для гуёвых :)

Под отсутствием UI я понимаю чисто функциональные модули, библиотеки например. В чистом виде юзер их никогда не увидит, просто не запустит, они не самодостаточны. Разработчик для таких вещей всегда пишет хотя бы минимальный враппер. Для себя я считаю хорошим тоном ни одно библиотечное исключение до пользователя не допускать. В худшем случае я заверну его в какой-нибудь MyApplication::UnknownException. Даже если библиотека моя собственная. При записи в лог там окажется всё дерево исключений, и я увижу причину. Пользователю же её знать во всех подробностях обычно не нужно, а мне меньше ломать голову над стилистикой сообщений, и тем более над переводом. И реже ресурсы править, что всё-таки лениво иногда делать. Всяких технических исключений обычно на порядок больше тех, что будут показаны пользователю как сообщения, так что время можно сильно сэкономить.
Честно говоря мне больше по душе вариант, когда используются политики отлова различных исключений, которые в с свою очередь передаются некоторому менеджеру и catch-блоке просто происходит нечто вроде exceptionManager->ProcessException(exception), а сами классы-исключения ничего об обработчиках не знают. В частности, это позволит таким же образом обрабатывать и стандартные исключения в том числе.
Главное не забыть написать все catch'и…
Достаточно написать один catch() для базового типа исключений. А exceptionManager внутри уже в зависимости от типа найдет для него зарегистрированный обработчик.
А как вы определите тип исключения?
Можно пытаться приводить через dynamic_cast в зарегистрированных обработчиках, чтобы определить подходит эксепшн или нет, есть вариант воспользоваться геморроем магией шаблонов. Правда в последнем случае не получится менять набор обработчиков в зависимости от конфигурации, например.
dynamic_cast одна из самых медленных операций в C++. А если еще учесть, что вам придется кастить 7 — 20 типов исключений, будет это не быстро…
Согласен. Но с другой стороны — если происходит много исключений в месте, где критична производительность, то это уже само по себе намекает на недостатки дизайна.
Есть ли замеры, которые показывает dynamic_cast узким местом?
Сейчас на руках у меня нет данных которые мне в свое время приводили. Но вот нашел другой источник.
Синтетические тесты это интересно, но хочется реального примера, когда dynamic_cast стал узким местом. Виртуальные функции тоже свой overhead имеют, надо от них отказаться? Я лично пирдерживаюсь подхода Саттера&Майерса и еще многих умных дядек — premature optimization is an evil thing.
Виртуальные функции тоже свой overhead имеют, надо от них отказаться?

Нет конечно. Просто как даже видно из таблиц лучше даже вызвать виртуальную функцию чем сделать dynamic_cast. ИМХО dynamic_cast лучше использовать в крайнем случае.
Не знаю, как там у вас в С++, но у нас в Delphi это можно сделать тремя способами:

if Instance is TMyVeryCoolObject then…
if Instance.InheritsFrom(TMyVeryCoolObject) then…
if Instance.ClassType = TMyVeryCoolObject then…
Ну по сути тоже самое и получается, просто Вы не используете Visitor.
Именно так. Но только исключения не знают об обработчике, что с моей точки зрения более правильно. Правда возникают вопросы по динамическому определению типа(см ветку выше) и какой вариант выбрать надо решать в зависимости от конкретных обстоятельств, я лишь изложил свою точку зрения.
Ну так и тут исключения ничего не знают о Visitor.
Они знают о том, что будут обработаны Visiter-ом
Да, извиняюсь. Я не внимательно посмотрел.
Честно говоря, не понял, зачем здесь класс Messenger? Почему нельзя в catch ловить базовый класс исключений и вызывать exception.accept(), в котором уже происходит вывод об ошибке?
Хочется знать и получить от автора: Прижилась ли идея визиторов в исключениях? Т.е. не выпилили Вы ее с течением времени? Статья была написана в далеком 2011-м, а уже 13 год и многое могло поменяться.
Ответил на ваш комментарий ниже.
Описанный мною подход мы используем практически как де-факто стандарт для исключений. Было придумано много мелких улучшений на основе этого подхода. Решение оправдало себя. Но как я говорил в статье:
В этой статье я попытался поделиться решением, которое как мне показалось, оптимальное для решения моих задач.

Тут ключевая фраза «для решения моих задач». Сейчас, оглядываясь на весь написанный код, видно что данный поход использован в основном для одной цели: вывести/достать сообщение об ошибки. И только где-то в 1-2 случаев из 100, такой подход понадобился для чего-то другого( конвертация одних исключений в другие ). В итоге, возможно, можно было упростить работу с исключениями и вместо метода accept в базовом классе сделать что-типа:
std::string getErrorText( ResourceManager & _res )

С другой стороны, визиторы дали нам достаточно гибкий механизм для обработки исключений, что позволило вынести «тематический» код в отдельный классы. Так вывод текста ошибки для всех исключений и все что с этим связано находится в одном классе, что очень удобно при расширении связанного с этим функционала.
В целом мое мнение, что данный подход себя оправдал, и у него больше преимуществ чем недостатков.
Sign up to leave a comment.

Articles