Следует ли использовать спецификатор исключения в 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

Я сомневаюсь, что действительно буду их использовать по следующим причинам:

  1. Компилятор на самом деле не применяет спецификаторы исключений каким-либо строгим образом, поэтому преимущества невелики. В идеале вы хотели бы получить ошибку компиляции.
  2. Если функция нарушает спецификатор исключения, я думаю, что стандартным поведением является завершение программы.
  3. В VS.Net он обрабатывает throw (X) как throw (...), поэтому соблюдение стандарта не является строгим.

Как вы думаете, следует ли использовать спецификаторы исключения?
Пожалуйста, ответьте «да» или «нет» и укажите причины, подтверждающие ваш ответ.

"throw (...)" не является стандартным C++. Я считаю, что это расширение в некоторых компиляторах и обычно имеет то же значение, что и спецификация исключения.

Richard Corden 18.09.2008 14:38
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
126
1
26 726
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

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

Да, ожидаемым поведением неуказанного исключения, генерируемого функцией со спецификаторами исключения, является вызов 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

Michael Burr 29.02.2012 02:14

@MichaelBurr Только throw(something) считается бесполезным и плохой идеей. throw() пригодится.

curiousguy 28.01.2017 20:59

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

curiousguy 28.01.2017 21:00

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

Ответ принят как подходящий

Нет.

Вот несколько примеров, почему:

  1. Код шаблона невозможно написать с указанием исключений,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }
    

    Копии могут генерироваться, передача параметров может вызывать, а x() может генерировать какое-то неизвестное исключение.

  2. Спецификации исключений имеют тенденцию запрещать расширяемость.

    virtual void open() throw( FileNotFound );
    

    может превратиться в

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );
    

    Вы действительно могли бы написать это как

    throw( ... )
    

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

  3. Устаревший код

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

    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() никогда не должен называться. Всегда лучше позволить приложению вылететь из-за необработанного исключения, из которого вы можете получить трассировку стека, чем молча / насильственно умереть.

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

    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 (). Это создает дамп ядра. Чем это хуже необработанного исключения? (что в основном делает то же самое)

Greg Rogers 18.09.2008 17:48

@ Грег Роджерс: Неперехваченное исключение по-прежнему выполняет раскрутку стека. Это означает, что будут вызываться деструкторы. И в этих деструкторах можно многое сделать, например: правильно освободить ресурсы, правильно записать журналы, другим процессам будет сообщено, что текущий процесс вылетает из строя и т. д. Подводя итог, это RAII.

paercebal 06.12.2008 02:53

Вы упустили: это эффективно оборачивает все в конструкцию try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ], независимо от того, хотите ли вы там блок try или нет.

David Thornley 01.03.2010 18:29

@paercebal Это неверно. Реализация определяет, запускаются ли деструкторы для неперехваченных исключений. В большинстве сред не разворачиваются деструкторы стека / запуска, если исключение не обнаружено. Если вы хотите обеспечить переносимость ваших деструкторов, даже если исключение выброшено и не обрабатывается (это сомнительное значение), вам нужно написать свой код, например try { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }.

Logan Capaldo 22.03.2010 00:00

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

curiousguy 28.01.2017 20:57

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++, но мне кажется, что они не связаны со спецификациями исключений.

David Thornley 01.03.2010 18:30

@ Дэвид Торнли: Я вижу гарантии такими, какими должны были быть спецификации исключения (т.е. метод с сильным G не может вызывать метод с базовым G без защиты). К сожалению, я не уверен, что они определены достаточно хорошо, чтобы их можно было использовать в компиляторе.

Martin York 01.03.2010 19:02

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

curiousguy 25.01.2017 23:22

@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 Не могу этого сделать. Поскольку я только что указал, вы не можете гарантировать, что исключение не будет сгенерировано.

Martin York 25.01.2017 23:27

@curiousguy: Но, как я сказал выше (в 2008 году), спецификации исключений были неудачным экспериментом. А в C++ 11 спецификации исключений были объявлены устаревшими. Теперь у нас есть noexcept для маркировки методов без метания (которые действуют как const (вы не можете вызвать метод метания из метода без метания)).

Martin York 25.01.2017 23:33

@LokiAstari "Вот как это делает Java, потому что проверки выполняются во время компиляции_" Спецификация исключений Java - это эксперимент, который потерпел неудачу. В Java нет наиболее полезной спецификации исключения: throw() (не генерирует). «Это только гарантирует, что если функция выдаст эти исключения, приложение завершит работу.» Нет, «неверно» означает истину. В C++ нет гарантии, что функция никогда не вызовет terminate(). «Поскольку я только что указал, вы не можете гарантировать, что исключение не будет сгенерировано» Вы гарантируете, что функция не выбросит. Это именно то, что вам нужно.

curiousguy 26.01.2017 01:27

от cppreference.com: спецификация динамического исключения "Возможные исключения Каждая функция f, указатель на функцию fp и указатель на функцию-член mfp имеет набор потенциальных исключений, который состоит из типов, которые могут быть выброшены. Набор всех типов указывает, что может быть создано любое исключение. Этот набор определяется следующим образом: 1) Если в объявлении f, fp или mfp используется throw () (не рекомендуется) или noexcept, набор пуст."

curiousguy 26.01.2017 01:50

@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

Martin York 27.01.2017 01:26

@curiousguy: С noexcept результатом остается вызов terminate(). Тонкое отличие от предыдущего состоит в том, что это определяется реализацией, если стек разматывается.

Martin York 27.01.2017 01:29

@curiousguy: Я не понял ни одного твоего комментария. Для меня они не имели смысла.

Martin York 27.01.2017 01:32

У меня создалось впечатление, что ваше предложение «Это только гарантирует, что если функция выдаст эти исключения, приложение завершит работу.» применимо только к спецификации динамических исключений (throw (some,list)), а не к nothrow. По этой причине я упомянул nothrow в своем предыдущем комментарии (который я сейчас удалил).

curiousguy 27.01.2017 05:21

Позвольте нам продолжить обсуждение в чате.

curiousguy 27.01.2017 05:23

Единственный полезный спецификатор исключения - это «throw ()», как в «не выбрасывает».

Не могли бы вы добавить причину, по которой это полезно?

buti-oxa 09.04.2009 22:48

почему это бесполезно? нет ничего более полезного, чем знать, что какая-то функция не начнет генерировать исключения слева направо и по центру.

Matt Joiner 27.10.2009 02:52

Пожалуйста, см. Обсуждение Херба Саттера, на которое ссылается Майкл Бёрр, для подробного объяснения.

Harold Ekstrom 28.10.2009 00:39

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

Кроме того, я считаю, что их использование устарело в текущих проектах стандарта C++ 0x.

Спецификации исключений - не очень полезные инструменты в C++. Однако для них есть / есть / хорошее применение в сочетании с std :: неожиданно.

В некоторых проектах я делаю код со спецификациями исключений, а затем вызываю set_unexpected () с функцией, которая генерирует специальное исключение моего собственного дизайна. Это исключение при построении получает обратную трассировку (в зависимости от платформы) и выводится из std :: bad_exception (чтобы разрешить его распространение при желании). Если это вызывает вызов terminate (), как это обычно бывает, трассировка распечатывается функцией what () (а также исходным исключением, вызвавшим его; это не сложно найти), и поэтому я получаю информацию о том, где был мой контракт. нарушено, например, возникло непредвиденное исключение библиотеки.

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

Спецификации исключения = мусор, спросите любого Java-разработчика старше 30 лет.

Программисты java старше 30 должны чувствовать себя плохо, что они не могут справиться с C, у них нет оправдания для использования java.

Matt Joiner 27.10.2009 02:53

Из статьи:

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

Другие вопросы по теме