Киньте ключевое слово в подпись функции - PullRequest
180 голосов
/ 28 июня 2009

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

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

Ответы [ 7 ]

115 голосов
/ 28 июня 2009

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

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

49 голосов
/ 28 июня 2009

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

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: За исключением, возможно, пустого, но на вашем месте я бы избежал даже этого.
27 голосов
/ 25 июня 2015

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

#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 исключением.test кроме.test.cpp
user @ user: ~ / tmp $ ./except.test
хорошо - это было неожиданно
прерывание вызывается после создания экземпляра int
Прервано (ядро сброшено)

10 голосов
/ 28 июня 2009

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

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

bool
some_func() /* throw (myExc) */ {
}
9 голосов
/ 24 июля 2014

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

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

8 голосов
/ 28 июня 2009

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

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

3 голосов
/ 07 июля 2017

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

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

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

Цитата из приведенной выше ссылки для спешащих (содержит пример плохих непреднамеренных эффектов указания throw для встроенной функции от наивного компилятора):

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

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

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

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

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

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

Не встроенная функция - это то место, где спецификация исключений «ничего не выбрасывает» может иметь некоторое преимущество с некоторыми компиляторами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...