Когда я должен действительно использовать noexcept?


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

  1. есть много примеров функций, которые я знаю, никогда не бросит, но для которых компилятор не может определить самостоятельно. Я должен добавить noexcept к объявлению функции в все такие случаи?

    нужно подумать о том, нужно ли мне добавлять noexcept после каждый объявление функции значительно снизило бы производительность программиста (и, честно говоря, было бы болью в заднице). За которую ситуации я должен быть более осторожным в использовании noexcept, и для каких ситуаций я могу уйти с подразумеваемым noexcept(false)?

  2. когда я могу реально ожидать улучшения производительности после использования noexcept? В частности, приведите пример кода, для которого компилятор C++ способен генерировать лучший машинный код после добавления noexcept.

    лично я забочусь о noexcept из-за большей свободы, предоставляемой компилятор для безопасного применения определенных видов оптимизации. Используют ли современные компиляторы noexcept в этом случае? Если нет, Могу ли я ожидать, что некоторые из них сделают это в ближайшем будущем?

9 373

9 ответов:

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

нужно подумать о том, нужно ли мне добавлять noexcept после каждого объявления функции значительно снизит производительность программиста (и, честно говоря, будет боль).

Ну тогда используйте его когда очевидно, что функция никогда не будет бросать.

когда я могу реально ожидать улучшения производительности после использования noexcept? [...] Лично я забочусь о noexcept из-за повышенной свободы, предоставляемой компилятору для безопасного применения определенных видов оптимизации.

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

используя noexcept в big 4 (конструкторы, назначение, а не деструкторы, как они уже noexcept), скорее всего, вызовет лучшие улучшения, как noexcept проверки являются "общими" в коде шаблона, например, в контейнерах std. Для например, std::vector не будет использовать ход вашего класса, если он не отмечен noexcept (или компилятор может вывести его иначе).

как я повторяю в эти дни:семантика первого.

добавлять noexcept,noexcept(true) и noexcept(false) в первую очередь о семантике. Это лишь случайно обусловливает ряд возможных оптимизаций.

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


хорошо, теперь о возможных оптимизациях.

наиболее очевидные оптимизации фактически выполняются в библиотеках. C++11 предоставляет ряд признаков, которые позволяют узнать, является ли функция noexcept или нет, и стандартная реализация библиотеки сама будет использовать эти черты в пользу noexcept операции с пользовательскими объектами, которыми они управляют, если это возможно. Такие как переместить семантика.

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

эти семантики были выбраны по двум причинам:

  • сразу же пользу от noexcept даже если зависимости не использовать (назад совместимость)
  • разрешение спецификации noexcept при вызове функций, которые теоретически могут бросать, но не ожидаются для заданных аргументов

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

почти каждая оптимизация в компиляторе использует что-то, называемое "потоковым графом" функции, чтобы рассуждать о том, что является законным. Потоковый график состоит из того, что обычно называют "блоками" функции (области кода, которые имеют один вход и один выход) и ребер между блоками, чтобы указать, куда поток может перейти. Noexcept изменяет график потока.

вы просили конкретный пример. Рассмотрим этот код:

void foo(int x) {
    try {
        bar();
        x = 5;
        // other stuff which doesn't modify x, but might throw
    } catch(...) {
        // don't modify x
    }

    baz(x); // or other statement using x
}

график потока для этой функции отличается, если bar с надписью noexcept (нет никакого способа для выполнения прыжка с конца bar и оператор catch). Когда помечено как noexcept, компилятор уверен, что значение x равно 5 во время функции baz - блок x=5, как говорят, "доминирует" над блоком baz(x) без края от bar() к оператору catch. Затем он может сделать что-то под названием "постоянное распространение" для создания более эффективного кода. Здесь, если baz встроен, операторы, использующие x, могут также содержать константы, а затем то, что раньше было оценкой времени выполнения, может быть превращено в оценку времени компиляции и т. д.

в любом случае, короткий ответ: noexcept позволяет компилятору генерировать более плотный график потока, и график потока используется для рассуждения о всех видах общих оптимизаций компилятора. Для компилятора пользовательские аннотации такого рода являются удивительными. Компилятор попытается понять этот материал, но обычно он не может (рассматриваемая функция может быть в другом объектном файле, не видимом компилятору, или транзитивно использовать какую-то функцию, которая не видна), или когда это происходит, есть какое-то тривиальное исключение, которое может быть брошено, что вы даже не зная об этом, он не может неявно обозначить его как noexcept (выделение памяти может бросить bad_alloc, например).

noexcept может значительно повысить производительность некоторых операций. Это происходит не на уровне генерации машинного кода компилятором, а путем выбора наиболее эффективного алгоритма: как уже упоминалось, вы делаете этот выбор с помощью функции std::move_if_noexcept. Например, рост std::vector (например, когда мы называем reserve) должен обеспечить сильное исключение-гарантию безопасности. Если он знает, что Tконструктор перемещения не бросает, он может просто перемещать каждый элемент. В противном случае он должен копировать все T s. Это было подробно описано в этот пост.

когда я могу реально, кроме как наблюдать улучшение производительности после использования noexcept? В частности, приведите пример кода, для которого компилятор C++ способен генерировать лучший машинный код после добавления noexcept.

Хм, никогда? Никогда не бывает времени? Никогда.

noexcept на компилятор оптимизация производительности таким же образом, что const для оптимизации производительности компилятора. То есть почти никогда.

noexcept в основном используется, чтобы позволить "вам" обнаружить во время компиляции, если функция может вызвать исключение. Помните: большинство компиляторов не выдают специальный код для исключений, если он на самом деле не бросает что-то. Так что noexcept дело не в том, чтобы дать компилятору подсказки о том, как оптимизировать функцию, а в том, чтобы дать вы советы о том, как использовать функцию.

Шаблоны типа move_if_noexcept определит, определен ли конструктор перемещения с помощью noexcept и вернет a const& вместо && типа если это не так. Это способ сказать, если это очень безопасно.

в общем, вы должны использовать noexcept когда вы думаете, что это будет полезное чтобы сделать так. Некоторые коды будут принимать разные пути, если is_nothrow_constructible правда для этого типа. Если вы используете код, который будет делать это, то не стесняйтесь noexcept соответствующие конструкторы.

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

  1. есть много примеров функций, которые я знаю, никогда не бросит, но для которых компилятор не может определить самостоятельно. Должен ли я добавлять noexcept к объявлению функции во всех таких случаях?

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

если у вас есть функция, которая не может бросить, спросите себя, будет ли он, как остановиться!--0--> или это ограничит будущие реализации? Например, вы можете ввести проверку ошибок незаконных аргументов путем создания исключений (например, для модульных тестов), или вы можете зависеть от другого кода библиотеки, который может изменить его спецификацию исключений. В этом случае безопаснее быть консервативным и опускать noexcept.

С другой стороны, если вы уверены, что функция никогда не должна бросать и правильно, что она является частью спецификации, вы должны объявить ее noexcept. Однако, имейте в виду, что компилятор не сможет обнаружить нарушения noexcept Если ваша реализация изменений.

  1. для каких ситуаций я должен быть более осторожным в использовании noexcept, и для каких ситуаций я могу получить прочь с подразумеваемым noexcept (false)?

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

  1. операции перемещения (оператор присваивания перемещения и конструкторы перемещения)
  2. операции своп
  3. deallocators памяти (оператор delete, оператор delete[])
  4. деструкторы (хотя они неявно noexcept(true) если вы не сделаете их noexcept(false))

эти функции обычно должны быть noexcept, и наиболее вероятно, что реализации библиотеки могут использовать noexcept собственность. Например, std::vector можно использовать не бросая операции перемещения без ущерба для сильных гарантий исключения. В противном случае ему придется вернуться к копированию элементов (как это было в C++98).

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

  1. когда я могу реально ожидать улучшения производительности после использования noexcept? В частности, приведите пример кода, для которого компилятор C++ способен генерировать лучший машинный код после добавления noexcept.

преимущество noexcept без исключения спецификации или throw() заключается в том, что стандарт позволяет составителям больше свободы, когда дело доходит до очистки стека. Даже в throw() case, компилятор должен полностью размотать стек (и он должен сделать это в точном обратном порядке конструкций объектов).

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

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

я также рекомендую книгу Скотта Мейерса " эффективный современный C++", "пункт 14: объявить функции noexcept, если они не будут выдавать исключения" для дальнейшего чтения.

в словах Бьярне:

где прекращение является приемлемым ответом, неперехваченным исключением достигнет этого, потому что он превращается в вызов terminate() (§13.5.2.5). Кроме того,noexcept описатель (§13.5.1.1) может сделать это желание явное.

успешные отказоустойчивые системы являются многоуровневыми. Каждый уровень справляется с таким количеством ошибок, как он может, не слишком искажается и уходит остальные на более высокие уровни. Исключения поддерживают это представление. Кроме того, terminate() поддерживает это представление, предоставляя escape, если сам механизм обработки исключений повреждены или если это было не полностью используется, таким образом оставляя исключения неперехваченными. Аналогично, noexcept обеспечивает простой побег для ошибок при попытке восстановления кажется неосуществимым.

 double compute(double x) noexcept;   {       
     string s = "Courtney and Anya"; 
     vector<double> tmp(10);      
     // ...   
 }

векторный конструктор может не получить память для своих десяти двойников и бросьте std::bad_alloc. В этом случае программа завершается. Оно завершается безоговорочно путем вызова std::terminate() (§30.4.1.3). Он не вызывает деструкторы из вызывающих функций. Это реализация-определяется ли деструкторы из областей между throw и noexcept (например, для s в вычислении ()) вызываются. Этот программа вот-вот завершится, поэтому мы не должны зависеть ни от кого в любом случае объект. добавить noexcept спецификатор, мы указываем, что наш код не был написан, чтобы справиться с бросать.

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

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

учитывая тот факт, что количество правил для работы ведьмы c++ постоянно растет и что мы жили счастливо без noexcept в течение многих лет... я думаю, что если кто-то не даст убийцу причины, почему использовать его никто не будет... или хотя бы каждый день c++ developers