Когда я действительно должен использовать noexcept?
на noexcept
ключевое слово может быть соответствующим образом применено ко многим сигнатурам функций, но я не уверен, когда я должен рассмотреть его использование на практике. Основываясь на том, что я прочитал до сих пор, в последнюю минуту добавление noexcept
кажется, для решения некоторых важных вопросов,которые возникают при перемещении конструкторов. Тем не менее, я все еще не могу дать удовлетворительные ответы на некоторые практические вопросы, которые заставили меня прочитать больше о noexcept
в первом место.
-
есть много примеров функций, которые я знаю, никогда не бросит, но для которых компилятор не может определить самостоятельно. Должен ли я добавить
noexcept
к объявлению функции в все такие случаи?необходимость думать о том, нужно ли мне добавлять
noexcept
после каждый объявление функции значительно уменьшило бы производительность программиста (и, честно говоря, было бы болью в заднице). За которую ситуации я должен быть более осторожным в использованииnoexcept
, и для каких ситуаций я могу уйти с подразумеваемымnoexcept(false)
? -
когда я могу реально ожидать улучшения производительности после использования
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
конструктор перемещения не бросает, он может просто перемещать каждый элемент. В противном случае он должен копировать все T
s. Это было подробно описано в этот пост.
когда я могу реалистично, кроме как наблюдать улучшение производительности после использования
noexcept
? В частности, приведем пример кода, для которого компилятор C++ способен генерировать лучший машинный код после добавления noexcept.
Хм, никогда? Никогда не бывает времени? Никогда.
noexcept
на компилятор оптимизация производительности таким же образом, что const
для оптимизации производительности компилятора. То есть почти никогда.
noexcept
в основном используется, чтобы позволить "вам" обнаруживать во время компиляции, если функция может вызвать исключение. Помните: большинство компиляторов не выдают специальный код для исключений, если он на самом деле не бросает что-то. Так что noexcept
дело не в том, чтобы дать компилятору подсказки о том, как оптимизировать функцию, а в том, чтобы дать вы советы по использованию функции.
Шаблоны типа move_if_noexcept
определит, определен ли конструктор перемещения с помощью noexcept
и вернется в const&
вместо &&
типа если это не так. Это способ сказать, если это очень безопасно.
в общем, вы должны использовать noexcept
когда вы думаете, что это будет полезное чтобы сделать так. Некоторый код будет принимать разные пути, если is_nothrow_constructible
правда для этого типа. Если вы используете код, который это сделает, то не стесняйтесь noexcept
соответствующие конструкторы.
вкратце: используйте его для конструкторов перемещения и похожие конструкции, но не чувствуйте, что вам нужно сходить с ума.
- есть много примеров функций, которые я знаю, никогда не бросит, но для которых компилятор не может определить самостоятельно. Должен ли я добавлять noexcept к объявлению функции во всех таких случаях?
noexcept
сложно, так как это часть интерфейса функций. Особенно, если вы пишете библиотеку, ваш клиентский код может зависеть от noexcept
собственность. Это может быть трудно изменить его позже, так как вы можете сломать существующее код. Это может быть меньше проблем, когда вы реализуете код, который используется только вашим приложением.
если у вас есть функция, которая не может бросить, спросите себя, будет ли он, как остановиться!--0--> или это ограничит будущие реализации? Например, вы можете ввести проверку ошибок незаконных аргументов путем создания исключений (например, для модульных тестов), или вы можете зависеть от другого кода библиотеки, который может изменить его спецификацию исключений. В этом случае безопаснее быть консервативным и опускать noexcept
.
С другой стороны, если вы уверены, что функция никогда не должна бросать, и правильно, что она является частью спецификации, вы должны объявить ее noexcept
. Однако, имейте в виду, что компилятор не сможет обнаружить нарушения noexcept
Если ваша реализация изменений.
- для каких ситуаций я должен быть более осторожным об использовании noexcept, и для каких ситуаций я могу получить прочь подразумеваемый noexcept (ложь)?
есть четыре класса функций, на которых вы должны сосредоточиться, потому что они, вероятно, окажут наибольшее влияние:
- операции перемещения (оператор назначения перемещения и конструкторы перемещения)
- операции своп
- deallocators памяти (оператор delete, оператор delete[])
- деструкторы (хотя они неявно
noexcept(true)
если вы не сделаете ихnoexcept(false)
)
эти функции обычно должны быть noexcept
, и, скорее всего, реализации библиотеки могут использовать noexcept
собственность. Например, std::vector
смогите использовать non-бросая деятельности движения без жертвовать сильные гарантии исключения. В противном случае ему придется вернуться к копированию элементов (как это было в C++98).
этот вид оптимизации находится на алгоритмическом уровне и не зависит от оптимизации компилятора. Он может иметь значительное влияние, особенно если элементы дороги для копирования.
- когда я могу реально ожидать улучшения производительности после использования 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++