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

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

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

7 ответов

Решение

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

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

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

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

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

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

Вывод GOTWs:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам