Вопрос по exception, c++ – Киньте ключевое слово в подпись функции

173

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

<code>bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}
</code>
Нет ничего плохого в том, чтобы иметь мнение о том, что связано с программированием. Этот закрывающий критерий плох, по крайней мере, для этого вопроса. Это интересный вопрос с интересными ответами. Anders Lindén
А также / Stackoverflow.com вопросы / 88573 / ... akauppi
См. Этот недавний связанный вопрос: / Stackoverflow.com вопросы / 1037575 / ... laalto
Следует отметить исключения спецификации устарела с C ++ 11. François Andrieux
Делаетnoexcept изменить что-нибудь? Aaron McDaid

Ваш Ответ

7   ответов
107

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

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

Но как насчет документации? Кроме того, вам сообщат, какие исключения вы никогда не поймаете, даже если попытаетесь. Anders Lindén
Да. Есть лучшие способы добавить пробел к вашему коду, чем throw (myEx). Assaf Lavie
yeah, люди, которые просто обнаруживают спецификации исключений, часто предполагают, что они работают как в Java, где компилятор может применять их. В C ++ этого не произойдет, что делает их намного менее полезными. jalf
Дело не в том, чтобы не применять его во время компиляции. В конце концов, Java-компиляторы могут применять его. Дело в том, что он не должен применяться во время компиляции. Функция полностью может генерировать другие исключения, но если это так, то должна вызываться непредвиденная (). Rob Kennedy
Я думаю, что код эффективен для документирования вашего кода. (комментарии могут врать). «Документативный» эффект, о котором я говорю, заключается в том, что вы будете точно знать, какие исключения вы можете поймать, а какие нет. Anders Lindén
2

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

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

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

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

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

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

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

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

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

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

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

10

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

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

bool
some_func() /* throw (myExc) */ {
}
- и что MSVC, по крайней мере, не реализует это поведение, насколько я знаю. jalf
Также полезно отметить, что вызов std :: непредвиденный () обычно приводит к вызову std :: terminate () и неожиданному завершению вашей программы. sth
45

Jalf уже связан с ним, но 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: За исключением, возможно, пустого, но на твоем месте я бы избегал даже этого.
@ Ken: Дело в том, что написание спецификаций исключений имеет в основном негативные последствия. Единственный положительный эффект заключается в том, что он показывает программисту, какие исключения могут возникнуть, но поскольку компилятор не проверяет его разумным образом, он подвержен ошибкам и поэтому не стоит много. sth
Хорошо, спасибо, что ответили. Я думаю, для этого есть документация. MasterMastic
Я не знаю, зачем мне выбрасывать исключение, и я не смог бы его упомянуть. Даже если он вызывается другой функцией, я знаю, какие исключения могут быть выданы. Единственная причина, которую я вижу, это то, что это утомительно. MasterMastic
Не верно. Должна быть написана спецификация исключения, но идея в том, чтобы сообщить, какие ошибки должен попытаться поймать вызывающи SmallChess
Как говорит @StudentT: функция обязана гарантировать, что не будут выбрасываться другие исключения. Если они это сделают, программа завершается как следует. Объявить бросок означает Я не обязан справляться с этой ситуацией и у вызывающей стороны должно быть достаточно информации для этого. Не объявлять исключения означает, что они могут возникать где угодно и с ними можно обращаться где угодно. Это, конечно, анти-ООП беспорядок. Это отказ проекта отлавливать исключения в неправильных местах. Я бы порекомендовал не выбрасывать пустой, так как исключения являются исключительными, и большинство функций в любом случае должны выбрасывать пустой. Jan Turoň
7

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

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

25

от вопрос, нужно потратить несколько минут на вопрос: каков вывод следующего кода?

#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.tes
хорошо - это было неожиданно
terminate вызывается после создания экземпляра i
Aborted (ядро сброшено)

7

- http: //blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.asp)

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

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

Похожие вопросы