Должен ли я использовать спецификатор исключений в C++?
В C++ вы можете указать, что функция может генерировать или не генерировать исключение, используя спецификатор исключения. Например:
void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type
Я сомневаюсь в том, чтобы фактически использовать их из-за следующего:
- Компилятор на самом деле не обеспечивает строгое указание исключений, поэтому преимущества невелики. В идеале вы хотели бы получить ошибку компиляции.
- Если функция нарушает спецификатор исключения, я думаю, что стандартное поведение - завершить программу.
- В VS.Net он рассматривает throw(X) как throw(...), поэтому соблюдение стандарта не является сильным.
Как вы думаете, следует использовать спецификаторы исключений?
Пожалуйста, ответьте "да" или "нет" и укажите несколько причин, чтобы оправдать ваш ответ.
14 ответов
Нет.
Вот несколько примеров, почему:
Код шаблона невозможно написать с указанием исключений,
template<class T> void f( T k ) { T x( k ); x.x(); }
Копии могут выдать, передача параметров может выдать, и
x()
может выдать какое-то неизвестное исключение.Спецификации исключений имеют тенденцию запрещать расширяемость.
virtual void open() throw( FileNotFound );
может развиться в
virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );
Вы могли бы действительно написать это как
throw( ... )
Первое не расширяемо, второе чрезмерно амбициозно, а третье действительно то, что вы имеете в виду, когда пишете виртуальные функции.
Устаревший код
Когда вы пишете код, основанный на другой библиотеке, вы на самом деле не знаете, что он может сделать, если что-то пойдет не так.
int lib_f(); void g() throw( k_too_small_exception ) { int k = lib_f(); if( k < 0 ) throw k_too_small_exception(); }
g
прекратится, когдаlib_f()
броски. Это (в большинстве случаев) не то, что вы действительно хотите.std::terminate()
никогда не должен называться. Всегда лучше позволить приложению аварийно завершить работу с необработанным исключением, из которого вы можете получить трассировку стека, чем молча / насильственно умереть.Написать код, который возвращает распространенные ошибки и выбрасывает в исключительных случаях.
Error e = open( "bla.txt" ); if( e == FileNotFound ) MessageUser( "File bla.txt not found" ); if( e == AccessDenied ) MessageUser( "Failed to open bla.txt, because we don't have read rights ..." ); if( e != Success ) MessageUser( "Failed due to some other error, error code = " + itoa( e ) ); try { std::vector<TObj> k( 1000 ); // ... } catch( const bad_alloc& b ) { MessageUser( "out of memory, exiting process" ); throw; }
Тем не менее, когда ваша библиотека просто генерирует ваши собственные исключения, вы можете использовать спецификации исключений, чтобы заявить о своем намерении.
Избегайте исключений спецификации в C++. Причины, которые вы приводите в своем вопросе, - хорошее начало для того, почему.
См. Херб Саттер "Прагматичный взгляд на спецификации исключений".
Я думаю, что стандартно, за исключением соглашения (для C++)
Спецификаторы исключений были экспериментом в стандарте C++, который в большинстве случаев не удался.
Исключением является то, что спецификатор no throw полезен, но вы также должны добавить соответствующий блок try catch, чтобы убедиться, что код соответствует спецификатору. У Херба Саттера есть страница на эту тему. Gotch 82
Кроме того, я думаю, что стоит описать Исключительные гарантии.
Это в основном документация о том, как на состояние объекта влияют исключения, экранирующие метод этого объекта. К сожалению, они не применяются или не упоминаются компилятором.
Повышение и исключения
Гарантия исключений
Без гарантии:
Нет гарантии о состоянии объекта после исключения метода из метода
В этих ситуациях объект больше не должен использоваться.
Основная гарантия:
Почти во всех ситуациях это должно быть минимальной гарантией, которую дает метод.
Это гарантирует, что состояние объекта хорошо определено и все еще может использоваться последовательно.
Сильная гарантия: (иначе транзакционная гарантия)
Это гарантирует, что метод будет успешно завершен
Или будет сгенерировано исключение, и состояние объектов не изменится.
Нет Броска Гарантия:
Метод гарантирует, что никакие исключения не могут распространяться за пределы метода.
Все деструкторы должны сделать эту гарантию.
| NB. Если исключение ускользает от деструктора, когда исключение уже распространяется
| приложение будет прекращено
gcc будет выдавать предупреждения при нарушении спецификаций исключений. Что я делаю, так это использую макросы для использования спецификаций исключений только в режиме "lint", который скомпилирован специально для проверки, чтобы убедиться, что исключения согласуются с моей документацией.
Единственный полезный спецификатор исключения - "throw()", как в "не бросает".
Спецификации исключений не являются удивительно полезными инструментами в C++. Тем не менее, есть / есть / хорошее применение для них, если в сочетании с std:: непредвиденным.
В некоторых проектах я делаю код со спецификациями исключений, а затем вызываю set_unexpected() с функцией, которая вызывает специальное исключение из моего собственного дизайна. Это исключение при построении получает обратную трассировку (в зависимости от платформы) и является производным от std::bad_exception (чтобы разрешить его распространение при желании). Если это вызывает вызов terminate(), как это обычно происходит, обратная трассировка печатается с помощью what() (а также исходное исключение, которое его вызвало; не трудно это найти), и поэтому я получаю информацию о том, где был мой контракт нарушено, например, возникла непредвиденная исключительная ситуация в библиотеке.
Если я сделаю это, я никогда не разрешу распространение исключений библиотеки (кроме std) и получу все мои исключения из std::exception. Если библиотека решит выбросить, я поймаю и преобразую в свою собственную иерархию, что позволит мне всегда контролировать код. Шаблонные функции, которые вызывают зависимые функции, должны избегать спецификаций исключений по очевидным причинам; но в любом случае редко иметь шаблонный интерфейс функций с библиотечным кодом (и лишь немногие библиотеки действительно используют шаблоны полезным способом).
Нет. Если вы используете их, и выдается исключение, которое вы не указали ни вашим кодом, ни кодом, вызываемым вашим кодом, то по умолчанию поведение заключается в немедленном завершении вашей программы.
Кроме того, я полагаю, что их использование устарело в текущих проектах стандарта C++0x.
Если вы пишете код, который будет использоваться людьми, которые предпочитают смотреть на объявление функции, а не на комментарии к ней, тогда спецификация скажет им, какие исключения они могут захотеть отловить.
В противном случае я не нахожу особенно полезным использовать что-либо, кроме throw()
чтобы указать, что он не выбрасывает никаких исключений.
Спецификация throw() позволяет компилятору выполнять некоторые оптимизации при анализе потока кода, если он знает, что функция никогда не вызовет исключение (или, по крайней мере, обещает никогда не вызывать исключение). Ларри Остерман кратко говорит об этом здесь:
http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx
Да, если вы во внутренней документации. Или, возможно, написание библиотеки, которую будут использовать другие, чтобы они могли сказать, что происходит, не обращаясь к документации. Бросать или не выбрасывать можно считать частью API, почти как возвращаемое значение.
Я согласен, они не очень полезны для обеспечения корректности стиля Java в компиляторе, но это лучше, чем ничего или случайные комментарии.
Они могут быть полезны для модульного тестирования, так что при написании тестов вы знаете, что ожидать от функции, если она выйдет из строя, но в компиляторе их нет. Я думаю, что это дополнительный код, который не нужен в C++. Что бы вы ни выбрали, все, в чем вы должны быть уверены, это то, что вы следуете одному и тому же стандарту кодирования для всего проекта и членов команды, чтобы ваш код оставался читаемым.
Вообще я бы не использовал спецификаторы исключений. Однако в тех случаях, когда из рассматриваемой функции должно исходить какое-либо другое исключение, которое программа не сможет исправить, это может оказаться полезным. Во всех случаях убедитесь, что вы четко документировали, какие исключения можно ожидать от этой функции.
Да, ожидаемое поведение неуказанного исключения, генерируемого из функции со спецификаторами исключений, заключается в вызове terminate().
Я также отмечу, что Скотт Мейерс рассматривает эту тему в более эффективном C++. Его книги "Эффективный С ++" и "Более эффективный С ++" - очень рекомендуемые книги.
Из статьи:
http://www.boost.org/community/exception_safety.html
"Хорошо известно, что невозможно написать универсальный контейнер, безопасный для исключений". Это утверждение часто звучит со ссылкой на статью Тома Каргилла [4], в которой он исследует проблему безопасности исключений для универсального шаблона стека. В своей статье Каргилл поднимает много полезных вопросов, но, к сожалению, не может предложить решение своей проблемы. В заключение он предполагает, что решение может быть невозможным. К сожалению, его статья была воспринята многими как "доказательство" этого предположения. Со времени публикации было много примеров безопасных для исключения универсальных компонентов, в том числе контейнеров стандартной библиотеки C++.
И действительно, я могу придумать, как сделать исключения из шаблонных классов безопасными. Если у вас нет контроля над всеми подклассами, у вас все равно могут возникнуть проблемы. Для этого можно создать typedef в ваших классах, которые определяют исключения, создаваемые различными классами шаблонов. Это думает, что проблема заключается в том, чтобы всегда решать ее позже, а не разрабатывать ее с самого начала, и я думаю, что эти накладные расходы являются настоящим препятствием.
Спецификации исключений = мусор, спросите любого разработчика Java старше 30 лет