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

В С++ вы можете указать, что функция может или не может генерировать исключение, используя спецификатор исключения. Например:

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

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

Ответ 1

Нет.

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

  • Код шаблона нельзя писать с спецификациями исключений,

    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

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

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

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

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

Нет гарантии:

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

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

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

Сильная гарантия: (ака Транзакционная гарантия)

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

Нет гарантии броска:

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

Ответ 4

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

Ответ 5

Единственным полезным спецификатором исключений является "throw()", как в "не throw".

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

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

Я также отмечу, что Скотт Мейерс рассматривает этот вопрос в "Более эффективном С++". Его эффективные С++ и более эффективные С++ - очень рекомендуемые книги.

Ответ 11

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

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

Ответ 12

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

Ответ 13

Из статьи:

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

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

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

Ответ 14

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