Бросить ключевое слово в подпись функции

Какова техническая причина, по которой считается неправильной практикой использовать ключевое слово С++ throw в сигнатуре функции?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}

Ответ 1

Нет, это не считается хорошей практикой. Напротив, это обычно считается плохой идеей.

http://www.gotw.ca/publications/mill22.htm более подробно объясняет, почему, но проблема частично в том, что компилятор не может обеспечить это, поэтому он должен быть проверен во время выполнения, что обычно нежелательно. И это никоим образом не поддерживается. (MSVC игнорирует спецификации исключений, за исключением throw(), которые он интерпретирует как гарантию того, что исключение не будет выбрано.

Ответ 2

Jalf уже связан с ним, но GOTW прекрасно объясняет, почему спецификации исключений не так полезны, как можно было бы надеяться:/p >

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

Правильны ли комментарии? Не совсем. Gunc() может действительно что-то бросить, а Hunc() может выбросить что-то другое, кроме A или B! Компилятор просто гарантирует победить их бессмысленно, если они это сделают... ах, и большую часть времени избивать свою программу бессмысленно.

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

Вывод GOTWs:

Таким образом, кажется, что лучший совет, который мы, как сообщество, изучил сегодня:

  • Мораль # 1: Никогда не пишите спецификацию исключения.
  • Мораль # 2: За исключением, возможно, пустого, но если бы я был вами, избегайте даже этого.

Ответ 3

Чтобы добавить немного больше значения для всего другого ответа на этот вопрос, нужно вложить несколько минут в вопрос: Каков вывод следующего кода?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

Ответ: Как отмечено здесь, программа вызывает std:: terminate(), и поэтому никто из обработчиков исключений не будет вызван. Подробности: вызывается первая функция my_unexpected(), но поскольку она не перебрасывает соответствующий тип исключения для прототипа функции throw_exception(), в конце вызывается std:: terminate(). Таким образом, полный вывод выглядит следующим образом:

user @user: ~/tmp $g++ -o except.test except.test.cpp
user @user: ~/tmp $./except.test
хорошо - это было неожиданно
terminate call после вызова экземпляра 'int'
Отменено (ядро сбрасывается)

Ответ 4

Единственный практический эффект спецификатора throw заключается в том, что если что-то отличное от myExc выбрано вашей функцией, будет вызван std::unexpected (вместо обычного механизма необработанных исключений).

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

bool
some_func() /* throw (myExc) */ {
}

Ответ 5

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

С С++ мое общее правило состоит в том, чтобы использовать только спецификации броска, чтобы указать, что метод не может выбрасывать. Это сильная гарантия. В противном случае предположим, что он может что-то бросить.

Ответ 6

Ну, пока я смотрел эту спецификацию броска, я взглянул на эту статью: - (http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx)

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

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

Когда компилятор видит это, с атрибутом "throw()", компилятор может полностью оптимизировать переменную "bar", потому что он знает, что исключение нельзя исключить из MethodThatCannotThrow(). Без атрибута throw() компилятор должен создать переменную "bar", потому что если MethodThatCannotThrow выдает исключение, обработчик исключений может/будет зависеть от значения барной переменной.

Кроме того, инструменты анализа исходного кода, такие как prefast, могут (и будут) использовать аннотацию throw() для улучшения возможностей обнаружения ошибок - например, если у вас есть try/catch, и все функции, которые вы вызываете, помечены как throw(), вам не нужен try/catch (да, это проблема, если позже вы вызовете функцию, которая может быть выбрана).

Ответ 7

Не спецификация throw для встроенной функции, которая возвращает только переменную-член и не может генерировать исключения, может быть использована некоторыми компиляторами для pesimizations (напротив оптимизации), которые могут отрицательно влиять на производительность. Это описано в литературе Boost: Exception-specification

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

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

Цитата из приведенной выше ссылки для тех, кто спешит:

Обоснование спецификации исключения

Спецификации исключений [ISO 15.4] иногда кодируются, чтобы указать, какие исключения могут быть выброшены, или потому, что программист надеется, что они улучшат производительность. Но рассмотрите следующий элемент из умного указателя:

Т & operator *() const throw() {return * ptr; }

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

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

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

Не встроенная функция - это одно место, которое "ничего не генерирует". Спецификация исключения может иметь некоторую выгоду для некоторых компиляторов.