Следует ли использовать спецификатор исключения в C ++?

123

В 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 (...), поэтому соблюдение стандарта не является строгим.

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

1800 ИНФОРМАЦИЯ
источник
7
"throw (...)" не является стандартным C ++. Я считаю, что это расширение в некоторых компиляторах и обычно имеет то же значение, что и спецификация исключения.
Ричард Корден,

Ответы:

97

Нет.

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

  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;
    }

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

Кристофер
источник
1
в 3 технически это будет std :: неожиданный, а не std :: terminate. Но когда эта функция вызывается, по умолчанию вызывается abort (). Это создает дамп ядра. Чем это хуже, чем необработанное исключение? (что в основном делает то же самое)
Грег Роджерс
6
@ Грег Роджерс: Неперехваченное исключение все еще разматывает стек. Это означает, что будут вызываться деструкторы. И в этих деструкторах можно многое сделать, например: правильно освободить ресурсы, правильно записать журналы, другим процессам будет сообщено, что текущий процесс вылетает из строя и т. Д. Подводя итог, это RAII.
paercebal 05
Вы упустили: это эффективно оборачивает все в try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]конструкцию, независимо от того, хотите ли вы там блок try или нет.
Дэвид Торнли
4
@paercebal Это неверно. Реализация определяет, запускаются ли деструкторы для неперехваченных исключений. В большинстве сред не разворачиваются деструкторы стека / запуска, если исключение не обнаружено. Если вы хотите обеспечить переносимость ваших деструкторов, даже если исключение выбрасывается и не обрабатывается (это сомнительное значение), вам нужно написать свой код, например,try { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Логан Капальдо
« Всегда лучше позволить приложению аварийно завершить работу с необработанным исключением, из которого вы можете получить трассировку стека, чем молча / насильственно умереть». Как может «сбой» быть лучше, чем чистый вызов terminate()? Почему бы просто не позвонить abort()?
curiousguy
42

Избегайте спецификаций исключений в C ++. Причины, по которым вы задаете свой вопрос, являются хорошим началом для объяснения причин.

См. «Прагматичный взгляд на спецификации исключений» Херба Саттера .

Майкл Берр
источник
3
@awoodland: использование 'dynamic-exception-спецификаций' ( throw(optional-type-id-list)) не рекомендуется в C ++ 11. Они все еще находятся в стандарте, но я думаю, что было отправлено предупреждение, что их использование следует тщательно продумать. C ++ 11 добавляет noexceptспецификацию и оператор. Я не знаю достаточно подробностей, noexceptчтобы прокомментировать это. Эта статья выглядит довольно подробной: akrzemi1.wordpress.com/2011/06/10/using-noexcept И у Дитмара Кюля есть статья в июньском журнале перегрузки 2011 года: accu.org/var/uploads/journals/overload103.pdf
Майкл Бёрр
Только @MichaelBurr throw(something)считается бесполезным и плохой идеей. throw()является полезным.
curiousguy
" Вот что многие люди думают о спецификациях исключений: bullet Гарантия того, что функции будут генерировать только перечисленные исключения (возможно, ни одного). Bullet Включить оптимизацию компилятора, основанную на знании того, что будут выбрасываться только перечисленные исключения (возможно, ни одного). опять же, обманчиво близок к тому, чтобы быть правильным »Нет, вышеприведенные ожидания абсолютно верны.
curiousguy
14

Я думаю, что стандартное исключение (для C ++)
спецификаторов исключений было экспериментом в стандарте C ++, который в основном провалился.
Исключением является то, что спецификатор no throw полезен, но вы также должны добавить соответствующий блок try catch внутри, чтобы убедиться, что код соответствует спецификатору. У Херба Саттера есть страница по этой теме. Gotch 82

В дополнение, я думаю, стоит описать гарантии исключений.

В основном это документация о том, как на состояние объекта влияют исключения, экранирующие метод этого объекта. К сожалению, компилятор не применяет их и не упоминает иным образом.
Повышение и исключения

Гарантии исключения

Без гарантии:

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

Основная гарантия:

Практически во всех ситуациях это должна быть минимальная гарантия, которую предоставляет метод.
Это гарантирует, что состояние объекта четко определено и его можно постоянно использовать.

Сильная гарантия: (также известная как транзакционная гарантия)

Это гарантирует, что метод завершится успешно. В
противном случае будет сгенерировано исключение и состояние объектов не изменится.

Гарантия отсутствия броска:

Этот метод гарантирует, что никакие исключения не могут распространяться за пределы метода.
Все деструкторы должны давать эту гарантию.
| NB Если исключение ускользает от деструктора, когда исключение уже распространяется
| приложение будет закрыто

Мартин Йорк
источник
Гарантии - это то, что должен знать каждый программист на C ++, но мне кажется, что они не связаны со спецификациями исключений.
Дэвид Торнли
1
@ Дэвид Торнли: Я вижу гарантии такими, какими должны были быть спецификации исключений (т. Е. Метод с сильным G не может вызывать метод с базовым G без защиты). К сожалению, я не уверен, что они определены достаточно хорошо, чтобы их можно было использовать в компиляторе.
Мартин Йорк
" Вот что многие люди думают о спецификациях исключений: - Гарантия того, что функции будут генерировать только перечисленные исключения (возможно, ни одного). - Включить оптимизацию компилятора на основе информации о том, что будут выброшены только перечисленные исключения (возможно, ни одного). Вышеуказанные ожидания таковы: опять же, обманчиво близок к тому, чтобы быть правильным ". На самом деле оба совершенно правы.
curiousguy
@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Не могу этого сделать. Поскольку я только что указал, вы не можете гарантировать, что исключение не будет создано.
Мартин Йорк
1
@LokiAstari «Вот как это делает Java, потому что проверки выполняются во время компиляции_» Спецификация исключений Java - это эксперимент, который провалился. В Java нет наиболее полезной спецификации исключений: throw()(не генерирует). « Это только гарантирует, что если функция вызовет эти исключения, приложение завершит работу. « Нет »не верно» означает «истина». В C ++ нет гарантии, что функция terminate()никогда не будет вызвана. « Поскольку я только что указал, вы не можете гарантировать, что исключение не будет сгенерировано». Вы гарантируете, что функция не сгенерирует. Это именно то, что вам нужно.
curiousguy
8

gcc выдаст предупреждения, если вы нарушите спецификации исключения. Что я делаю, так это использую макросы для использования спецификаций исключений только в режиме компиляции "lint" специально для проверки, чтобы убедиться, что исключения согласуются с моей документацией.

Джереми
источник
7

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

Гарольд Экстром
источник
2
Не могли бы вы добавить причину, почему это полезно?
buti-oxa
2
почему это не полезно? нет ничего более полезного, чем знать, что какая-то функция не начнет генерировать исключения слева направо и по центру.
Мэтт Джойнер,
1
Пожалуйста, см. Обсуждение Херба Саттера, на которое ссылается Майкл Берр, для подробного объяснения.
Гарольд Экстрем,
4

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

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

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

coppro
источник
3

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

В противном случае я не считаю особенно полезным использовать что-либо, кроме throw()как указать, что это не вызывает никаких исключений.

Бранан
источник
3

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

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

Ферруччо
источник
3

Спецификация throw () позволяет компилятору выполнять некоторые оптимизации при анализе потока кода, если он знает, что функция никогда не вызовет исключение (или, по крайней мере, обещает никогда не генерировать исключение). Ларри Остерман вкратце рассказывает об этом здесь:

http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx

Сок
источник
2

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

Да, ожидаемым поведением неуказанного исключения, генерируемого функцией со спецификаторами исключения, является вызов terminate ().

Замечу также, что Скотт Мейерс рассматривает эту тему в «Более эффективном C ++». Его книги «Эффективный C ++» и «Более эффективный C ++» очень рекомендуются.

Крис Кумлер
источник
2

Да, если вам нужна внутренняя документация. Или, может быть, написать библиотеку, которую будут использовать другие, чтобы они могли знать, что происходит, не обращаясь к документации. Бросок или не бросание можно рассматривать как часть API, почти как возвращаемое значение.

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

user10392
источник
2

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

Странный
источник
0

Из статьи:

http://www.boost.org/community/exception_safety.html

«Хорошо известно, что невозможно написать безопасный универсальный контейнер». Это утверждение часто можно услышать со ссылкой на статью Тома Каргилла [4], в которой он исследует проблему безопасности исключений для универсального шаблона стека. В своей статье Каргилл поднимает много полезных вопросов, но, к сожалению, не предлагает решения своей проблемы1. В заключение он высказывает предположение, что решение может быть невозможно. К сожалению, его статья была воспринята многими как «доказательство» этого предположения. С момента его публикации появилось множество примеров безопасных в отношении исключений универсальных компонентов, в том числе контейнеров стандартной библиотеки C ++.

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

Marius
источник
-2

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

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