Должен ли я использовать спецификатор исключений в C ++? - PullRequest
119 голосов
/ 18 сентября 2008

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

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

Ответы [ 14 ]

91 голосов
/ 18 сентября 2008

номер

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

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

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

42 голосов
/ 18 сентября 2008

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

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

14 голосов
/ 18 сентября 2008

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

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

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

Исключительные гарантии

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

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

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

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

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

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

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

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

8 голосов
/ 18 сентября 2008

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

7 голосов
/ 19 сентября 2008

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

4 голосов
/ 20 октября 2008

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

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

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

4 голосов
/ 19 сентября 2008

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

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

3 голосов
/ 01 марта 2010

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

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

3 голосов
/ 18 сентября 2008

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

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

2 голосов
/ 18 сентября 2008

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

...