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

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

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

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

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

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

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 операции на пользовательские они манипулируют, если возможных объектов. Такие as переместить семантика.

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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