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


200

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

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

Смотрите этот недавний связанный с этим вопрос: stackoverflow.com/questions/1037575/...
laalto


1
Что- noexceptнибудь меняет?
Аарон МакДейд

12
Нет ничего плохого в том, чтобы иметь мнение о том, что связано с программированием. Этот закрывающий критерий плох, по крайней мере, для этого вопроса. Это интересный вопрос с интересными ответами.
Андерс Линден

Следует отметить, что спецификации исключений устарели с C ++ 11.
Франсуа Андриё

Ответы:


127

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

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


25
Да. Есть лучшие способы добавить пробел к вашему коду, чем throw (myEx).
Ассаф Лави

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

7
Но как насчет документальной цели тогда? Кроме того, вам сообщат, какие исключения вы никогда не поймаете, даже если попытаетесь.
Андерс Линден

1
@ AndersLindén, какие документальные цели? Если вы просто хотите задокументировать поведение вашего кода, просто поместите над ним комментарий. Не уверен, что вы имеете в виду со второй частью. Исключения вы никогда не поймаете, даже если попытаетесь?
jalf

4
Я думаю, что код является мощным, когда дело доходит до документирования вашего кода. (комментарии могут врать). «Документативный» эффект, о котором я говорю, заключается в том, что вы будете точно знать, какие исключения вы можете поймать, а какие нет.
Андерс Линден

57

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

1
Я не уверен, зачем я выбрасываю исключение и не смог бы его упомянуть. Даже если он вызывается другой функцией, я знаю, какие исключения могут быть выданы. Единственная причина, которую я вижу, это то, что это утомительно.
MasterMastic

3
@Ken: Дело в том, что написание спецификаций исключений имеет в основном негативные последствия. Единственный положительный эффект заключается в том, что он показывает программисту, какие исключения могут возникнуть, но поскольку компилятор не проверяет его разумным образом, он подвержен ошибкам и, следовательно, не стоит много.
STH

1
О, хорошо, спасибо за ответ. Я думаю, для этого есть документация.
MasterMastic

2
Не верно. Спецификация исключений должна быть написана, но идея состоит в том, чтобы сообщить, какие ошибки должен попытаться поймать вызывающий объект.
HelloWorld,

1
Как и @StudentT, функция отвечает за то, чтобы гарантировать, что не будут выбрасываться другие исключения. Если они это сделают, программа завершается как следует. Объявлять throw означает, что я не обязан решать эту ситуацию, и вызывающий должен иметь достаточно информации для этого. Не объявлять исключения означает, что они могут возникать где угодно и с ними можно обращаться где угодно. Это, безусловно, анти-ООП беспорядок. Это отказ проекта отлавливать исключения в неправильных местах. Я бы порекомендовал не выбрасывать пустое значение, поскольку исключения являются исключительными, и большинство функций должно выдавать пустое значение в любом случае.
Jan Turoň

30

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

#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
Прервано (ядро) сбрасывали)


12

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

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

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

5
Также полезно отметить, что вызов std :: непредвиденный () обычно приводит к вызову std :: terminate () и внезапному завершению вашей программы.
STH

1
- и что MSVC, по крайней мере, не реализует это поведение, насколько я знаю.
Джалф

9

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

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


9

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


3

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

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

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

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

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

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

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

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

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

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

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


1
«Самая большая проблема со спецификациями исключений заключается в том, что программисты используют их так, как если бы они имели эффект, который хотел бы получить программист, а не эффект, который они фактически имеют». Это причина ошибок №1 при общении с машинами или людьми: разница между тем, что мы говорим, и тем, что мы имеем в виду.
Thagomizer
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.