В 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
Я сомневаюсь, что действительно буду их использовать по следующим причинам:
Как вы думаете, следует ли использовать спецификаторы исключения?
Пожалуйста, ответьте «да» или «нет» и укажите причины, подтверждающие ваш ответ.
Обычно я бы не стал использовать спецификаторы исключений. Однако в случаях, когда из рассматриваемой функции должно исходить какое-либо другое исключение, что программа окончательно не сможет выполнить правильный, это может быть полезно. Во всех случаях обязательно четко документируйте, каких исключений можно ожидать от этой функции.
Да, ожидаемым поведением неуказанного исключения, генерируемого функцией со спецификаторами исключения, является вызов terminate ().
Замечу также, что Скотт Мейерс рассматривает эту тему в «Более эффективном C++». Его книги «Эффективный C++» и «Более эффективный C++» очень рекомендуются.
Если вы пишете код, который будет использоваться людьми, которые предпочли бы смотреть на объявление функции, чем на какие-либо комментарии к нему, тогда спецификация сообщит им, какие исключения они могут захотеть перехватить.
В противном случае я не считаю особенно полезным использовать что-либо, кроме throw()
, чтобы указать, что он не генерирует никаких исключений.
Да, если вам нужна внутренняя документация. Или, может быть, написать библиотеку, которую будут использовать другие, чтобы они могли знать, что происходит, не обращаясь к документации. Бросок или не бросание можно рассматривать как часть API, почти как возвращаемое значение.
Я согласен, они не очень полезны для обеспечения корректности стиля Java в компиляторе, но это лучше, чем ничего или случайные комментарии.
Избегайте спецификаций исключений в C++. Причины, которые вы указываете в своем вопросе, - довольно хорошее начало для объяснения того, почему.
См. «Прагматичный взгляд на спецификации исключений» Херба Саттера.
@awoodland: использование динамических спецификаций исключений (throw(optional-type-id-list)
) не рекомендуется в C++ 11. Они все еще находятся в стандарте, но я думаю, что было отправлено предупреждение, что их использование следует тщательно продумать. C++ 11 добавляет спецификацию и оператор noexcept
. Я не знаю достаточно подробностей о noexcept
, чтобы его прокомментировать. Эта статья выглядит довольно подробной: akrzemi1.wordpress.com/2011/06/10/using-noexcept И Дитмар Кюль опубликовал статью в журнале Overload за июнь 2011 года: accu.org/var/uploads/journals/overload103.pdf
@MichaelBurr Только throw(something)
считается бесполезным и плохой идеей. throw()
пригодится.
"Вот что многие думают о спецификациях исключений: bullet Гарантия того, что функции будут генерировать только перечисленные исключения (возможно, ни одного). bullet Включить оптимизацию компилятора, зная, что будут выбрасываться только перечисленные исключения (возможно, ни одного). Вышеупомянутые ожидания снова обманчиво близки к тому, чтобы быть правильными." Нет, вышеуказанные ожидания верны.
Они могут быть полезны для модульного тестирования, так что при написании тестов вы знаете, чего ожидать от функции, когда она терпит неудачу, но в компиляторе их не применяют. Я думаю, что это лишний код, который не нужен в C++. Что бы вы ни выбрали, все, в чем вы должны быть уверены, - это то, что вы следуете одному и тому же стандарту кодирования во всем проекте и для членов команды, чтобы ваш код оставался читаемым.
Нет.
Вот несколько примеров, почему:
Код шаблона невозможно написать с указанием исключений,
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;
}
Тем не менее, когда ваша библиотека просто генерирует ваши собственные исключения, вы можете использовать спецификации исключений, чтобы заявить о своем намерении.
в 3 технически это будет std :: неожиданный, а не std :: terminate. Но когда эта функция вызывается, по умолчанию вызывается abort (). Это создает дамп ядра. Чем это хуже необработанного исключения? (что в основном делает то же самое)
@ Грег Роджерс: Неперехваченное исключение по-прежнему выполняет раскрутку стека. Это означает, что будут вызываться деструкторы. И в этих деструкторах можно многое сделать, например: правильно освободить ресурсы, правильно записать журналы, другим процессам будет сообщено, что текущий процесс вылетает из строя и т. д. Подводя итог, это RAII.
Вы упустили: это эффективно оборачивает все в конструкцию try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]
, независимо от того, хотите ли вы там блок try или нет.
@paercebal Это неверно. Реализация определяет, запускаются ли деструкторы для неперехваченных исключений. В большинстве сред не разворачиваются деструкторы стека / запуска, если исключение не обнаружено. Если вы хотите обеспечить переносимость ваших деструкторов, даже если исключение выброшено и не обрабатывается (это сомнительное значение), вам нужно написать свой код, например try { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
.
"Всегда лучше позволить приложению вылететь из-за необработанного исключения, из которого вы можете получить трассировку стека, чем молча / насильственно умереть." Как может "сбой" быть лучше, чем чистый вызов terminate()
? Почему бы просто не вызвать abort()
?
gcc выдаст предупреждения, если вы нарушите спецификации исключения. Что я делаю, так это использую макросы для использования спецификаций исключений только в режиме компиляции "lint" специально для проверки, чтобы убедиться, что исключения согласуются с моей документацией.
Я думаю, что стандартно кроме соглашения (для C++)
Спецификаторы исключений были экспериментом в стандарте C++, который в большинстве случаев провалился.
Исключением является то, что спецификатор no throw полезен, но вы также должны добавить соответствующий блок try catch внутри, чтобы убедиться, что код соответствует спецификатору. У Херба Саттера есть страница по этой теме. Gotch 82
В дополнение, я думаю, стоит описать гарантии исключений.
Это в основном документация о том, как на состояние объекта влияют исключения, экранирующие метод этого объекта. К сожалению, компилятор не применяет их и не упоминает иным образом. Повышение и исключения
There is no guarantee about the state of the object after an exception escapes a method
In these situations the object should no longer be used.
In nearly all situations this should be the minimum guarantee a method provides.
This guarantees the object's state is well defined and can still be consistently used.
This guarantees that the method will complete successfully
Or an Exception will be thrown and the objects state will not change.
The method guarantees that no exceptions are allowed to propagate out of the method.
All destructors should make this guarantee.
| N.B. If an exception escapes a destructor while an exception is already propagating
| the application will terminate
Гарантии - это то, что должен знать каждый программист на C++, но мне кажется, что они не связаны со спецификациями исключений.
@ Дэвид Торнли: Я вижу гарантии такими, какими должны были быть спецификации исключения (т.е. метод с сильным G не может вызывать метод с базовым G без защиты). К сожалению, я не уверен, что они определены достаточно хорошо, чтобы их можно было использовать в компиляторе.
"Вот что многие думают о спецификациях исключений: - Гарантия того, что функции будут генерировать только перечисленные исключения (возможно, ни одного). - Включите оптимизацию компилятора, зная, что будут выброшены только перечисленные исключения (возможно, ни одного). Вышеупомянутые ожидания снова обманчиво близки к правильности." На самом деле оба точно правы.
@curiousguy. Вот как это делает Java, потому что проверки выполняются во время компиляции. C++ - это проверки во время выполнения. Итак: Guarantee that functions will only throw listed exceptions (possibly none)
. Не правда. Это только гарантирует, что если функция выдаст эти исключения, приложение завершит свою работу. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrown
Не могу этого сделать. Поскольку я только что указал, вы не можете гарантировать, что исключение не будет сгенерировано.
@curiousguy: Но, как я сказал выше (в 2008 году), спецификации исключений были неудачным экспериментом. А в C++ 11 спецификации исключений были объявлены устаревшими. Теперь у нас есть noexcept
для маркировки методов без метания (которые действуют как const (вы не можете вызвать метод метания из метода без метания)).
@LokiAstari "Вот как это делает Java, потому что проверки выполняются во время компиляции_" Спецификация исключений Java - это эксперимент, который потерпел неудачу. В Java нет наиболее полезной спецификации исключения: throw()
(не генерирует). «Это только гарантирует, что если функция выдаст эти исключения, приложение завершит работу.» Нет, «неверно» означает истину. В C++ нет гарантии, что функция никогда не вызовет terminate()
. «Поскольку я только что указал, вы не можете гарантировать, что исключение не будет сгенерировано» Вы гарантируете, что функция не выбросит. Это именно то, что вам нужно.
от cppreference.com: спецификация динамического исключения "Возможные исключения Каждая функция f, указатель на функцию fp и указатель на функцию-член mfp имеет набор потенциальных исключений, который состоит из типов, которые могут быть выброшены. Набор всех типов указывает, что может быть создано любое исключение. Этот набор определяется следующим образом: 1) Если в объявлении f, fp или mfp используется throw () (не рекомендуется) или noexcept, набор пуст."
@curiousguy: N3690 устарел почти на два года: Текущая версия стандарта: N4618 open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4618.pdf или github.com/cplusplus/draft/blob/master/papers/n4618.pdf Вот список стандартных версий: stackoverflow.com/a/4653479/14065
@curiousguy: С noexcept
результатом остается вызов terminate()
. Тонкое отличие от предыдущего состоит в том, что это определяется реализацией, если стек разматывается.
@curiousguy: Я не понял ни одного твоего комментария. Для меня они не имели смысла.
У меня создалось впечатление, что ваше предложение «Это только гарантирует, что если функция выдаст эти исключения, приложение завершит работу.» применимо только к спецификации динамических исключений (throw (some,list)
), а не к nothrow
. По этой причине я упомянул nothrow
в своем предыдущем комментарии (который я сейчас удалил).
Позвольте нам продолжить обсуждение в чате.
Единственный полезный спецификатор исключения - это «throw ()», как в «не выбрасывает».
Не могли бы вы добавить причину, по которой это полезно?
почему это бесполезно? нет ничего более полезного, чем знать, что какая-то функция не начнет генерировать исключения слева направо и по центру.
Пожалуйста, см. Обсуждение Херба Саттера, на которое ссылается Майкл Бёрр, для подробного объяснения.
Нет. Если вы их используете и генерирует исключение, которое вы не указали, либо вашим кодом, либо кодом, вызванным вашим кодом, то поведение по умолчанию - незамедлительно завершить вашу программу.
Кроме того, я считаю, что их использование устарело в текущих проектах стандарта C++ 0x.
Спецификации исключений - не очень полезные инструменты в C++. Однако для них есть / есть / хорошее применение в сочетании с std :: неожиданно.
В некоторых проектах я делаю код со спецификациями исключений, а затем вызываю set_unexpected () с функцией, которая генерирует специальное исключение моего собственного дизайна. Это исключение при построении получает обратную трассировку (в зависимости от платформы) и выводится из std :: bad_exception (чтобы разрешить его распространение при желании). Если это вызывает вызов terminate (), как это обычно бывает, трассировка распечатывается функцией what () (а также исходным исключением, вызвавшим его; это не сложно найти), и поэтому я получаю информацию о том, где был мой контракт. нарушено, например, возникло непредвиденное исключение библиотеки.
Если я сделаю это, я никогда не разрешаю распространение исключений библиотеки (кроме std) и получаю все свои исключения из std :: exception. Если библиотека решит бросить, я поймаю и конвертирую в свою собственную иерархию, что позволит мне всегда контролировать код. Шаблонные функции, вызывающие зависимые функции, должны избегать спецификаций исключений по очевидным причинам; но в любом случае редко бывает шаблонный интерфейс функции с кодом библиотеки (и несколько библиотек действительно используют шаблоны полезным образом).
Спецификации исключения = мусор, спросите любого Java-разработчика старше 30 лет.
Программисты java старше 30 должны чувствовать себя плохо, что они не могут справиться с C, у них нет оправдания для использования java.
Из статьи:
http://www.boost.org/community/exception_safety.html
“It is well known to be impossible to write an exception-safe generic container.” This claim is often heard with reference to an article by Tom Cargill [4] in which he explores the problem of exception-safety for a generic stack template. In his article, Cargill raises many useful questions, but unfortunately fails to present a solution to his problem.1 He concludes by suggesting that a solution may not be possible. Unfortunately, his article was read by many as “proof” of that speculation. Since it was published there have been many examples of exception-safe generic components, among them the C++ standard library containers.
И действительно, я могу придумать способы сделать исключения шаблонных классов безопасными. Если у вас нет контроля над всеми подклассами, у вас все равно может возникнуть проблема. Для этого можно создать в своих классах определения типов, которые определяют исключения, создаваемые различными классами шаблонов. Я думаю, что проблема, как всегда, в том, чтобы решать ее впоследствии, а не проектировать ее с самого начала, и я думаю, что именно эти накладные расходы являются настоящим препятствием.
Спецификация throw () позволяет компилятору выполнять некоторые оптимизации при анализе потока кода, если он знает, что функция никогда не вызовет исключение (или, по крайней мере, обещает никогда не генерировать исключение). Ларри Остерман вкратце рассказывает об этом здесь:
http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx
"throw (...)" не является стандартным C++. Я считаю, что это расширение в некоторых компиляторах и обычно имеет то же значение, что и спецификация исключения.