noexcept
Ключевое слово может быть соответствующим образом применено ко многим сигнатуры функций, но я не уверен , о том , когда следует рассмотреть возможность использования его на практике. Основываясь на том, что я прочитал, добавление в последнюю минуту, noexcept
кажется, решает некоторые важные проблемы, возникающие при броске конструкторов перемещения. Тем не менее, я до сих пор не могу дать удовлетворительные ответы на некоторые практические вопросы, которые побудили меня noexcept
сначала прочитать больше об этом.
Есть много примеров функций, которые, я знаю, никогда не сгенерируют, но для которых компилятор не может определить это самостоятельно. Должен ли я добавить
noexcept
к объявлению функции во всех таких случаях?Необходимость думать о том, нужно ли мне добавлять
noexcept
после каждого объявления функции, значительно снизит производительность программиста (и, честно говоря, это будет боль в заднице). Для каких ситуаций я должен быть более осторожным в использованииnoexcept
, и для каких ситуаций я могу избежать неприятных последствийnoexcept(false)
?Когда можно реально ожидать улучшения производительности после использования
noexcept
? В частности, приведите пример кода, для которого компилятор C ++ способен генерировать лучший машинный код после добавленияnoexcept
.Лично я забочусь о том,
noexcept
что компилятору предоставляется больше свободы для безопасного применения определенных видов оптимизаций. Используют ли современные компиляторыnoexcept
таким образом? Если нет, могу ли я ожидать, что некоторые из них сделают это в ближайшем будущем?
move_if_nothrow
(или whatchamacallit), увидит улучшение производительности, если есть не только ход ctor.move_if_noexcept
.Ответы:
Я думаю, что слишком рано давать ответ на этот вопрос "передового опыта", так как не было достаточно времени, чтобы использовать его на практике. Если бы их спросили о спецификаторах броска сразу после того, как они вышли, тогда ответы были бы совсем другими.
Хорошо, тогда используйте его, когда очевидно, что функция никогда не сгенерирует.
Похоже, что наибольшую выгоду от оптимизации получают пользовательские оптимизации, а не компиляторы из-за возможности их проверки
noexcept
и перегрузки. Большинство компиляторов следуют методу обработки исключений без штрафов, если вы не выбросили, поэтому я сомневаюсь, что это сильно изменит (или что-нибудь) на уровне машинного кода вашего кода, хотя, возможно, уменьшит размер двоичного кода, удалив обработка кода.Использование
noexcept
в большой четверке (конструкторы, присваивания, а не деструкторы, как они уже естьnoexcept
), вероятно, приведет к лучшим улучшениям, посколькуnoexcept
проверки являются «общими» в коде шаблона, например вstd
контейнерах. Например,std::vector
не будет использовать ход вашего класса, если он не помеченnoexcept
(или компилятор может вывести его иначе).источник
std::terminate
уловка все еще подчиняется модели с нулевой стоимостью. То есть, так уж получилось, что диапазон инструкций внутриnoexcept
функций отображается для вызоваstd::terminate
if, еслиthrow
вместо разматывания стека используется. Поэтому я сомневаюсь, что у него больше накладных расходов, чем при обычном отслеживании исключений.noexcept
гарантирует.noexcept
вызывается функция throw, тоstd::terminate
вызывается, что, по-видимому, связано с небольшим объемом служебной информации»… Нет, это следует реализовать, не генерируя таблицы исключений для такой функции, которую диспетчер исключений должен перехватить и затем выручить.noexcept
является частью интерфейса функции ; Вы не должны добавлять его только потому, что ваша текущая реализация не генерируется. Я не уверен в правильном ответе на этот вопрос, но я совершенно уверен, что то, как ваша функция ведет себя сегодня, не имеет к этому никакого отношения ...Как я повторяю в эти дни: семантика в первую очередь .
Добавление
noexcept
,noexcept(true)
иnoexcept(false)
это в первую очередь о семантике. Это лишь случайное условие ряда возможных оптимизаций.Как программист, читающий код, его присутствие
noexcept
похоже наconst
: оно помогает мне лучше понять, что может случиться, а что нет. Следовательно, стоит потратить некоторое время на размышления о том, знаете ли вы, если функция сработает. Для напоминания, любой вид динамического выделения памяти может бросить.Хорошо, теперь о возможных оптимизациях.
Наиболее очевидные оптимизации на самом деле выполняются в библиотеках. C ++ 11 предоставляет ряд характеристик, позволяющих узнать, является ли функция функцией
noexcept
, и сама реализация Стандартной библиотеки будет использовать эти характеристики для поддержкиnoexcept
операций с пользовательскими объектами, которыми они манипулируют, если это возможно. Например, семантика перемещения .Компилятор может только немного избавиться (возможно) от данных обработки исключений, потому что он должен учитывать тот факт, что вы могли солгать. Если функция помечена
noexcept
как throw, тоstd::terminate
вызывается.Эта семантика была выбрана по двум причинам:
noexcept
даже если зависимости не используют его (обратная совместимость)noexcept
при вызове функций, которые теоретически могут генерировать, но не ожидаются для данных аргументовисточник
noexcept
функции, не должна делать ничего особенного, потому что любые исключения, которые могут возникнуть, срабатываютterminate
до того, как они достигнут этого уровня. Это сильно отличается от необходимости разбираться и распространятьbad_alloc
исключение.noexcept
полезную / хорошую идею. Я начинаю думать, что перемещение конструкции, перемещение задания и своп - единственные случаи, которые есть ... Знаете ли вы о каких-либо других?noexcept
кто-то может уверенно использовать его для фрагмента данных, к которому впоследствии можно будет получить доступ. Я мог видеть эту идею где-то в другом месте, но стандартная библиотека довольно слабая в C ++, и я думаю, что она используется только для оптимизации копий элементов.Это действительно (потенциально) имеет огромное значение для оптимизатора в компиляторе. Компиляторы на самом деле имели эту функцию в течение многих лет с помощью пустого оператора throw () после определения функции, а также расширений. Я могу заверить вас, что современные компиляторы используют эти знания для создания лучшего кода.
Почти каждая оптимизация в компиляторе использует то, что называется «потоковым графом» функции, чтобы рассуждать о том, что является законным. Потоковый граф состоит из так называемых «блоков» функции (областей кода, которые имеют один вход и один выход) и ребер между блоками, чтобы указать, куда может перейти поток. Noexcept изменяет потоковый граф.
Вы просили конкретный пример. Рассмотрим этот код:
Граф потока для этой функции отличается, если
bar
помеченnoexcept
(у выполнения нет способа перейти между концомbar
и оператором catch). При обозначении какnoexcept
, компилятор уверен, что значение x равно 5 во время функции baz - говорят, что блок x = 5 «доминирует» над блоком baz (x) без ребра изbar()
оператора catch.Затем он может сделать то, что называется «постоянное распространение», чтобы генерировать более эффективный код. Здесь, если baz встроен, операторы, использующие x, могут также содержать константы, а затем то, что раньше было оценкой во время выполнения, может быть превращено в оценку во время компиляции и т. Д.
В любом случае, краткий ответ:
noexcept
позволяет компилятору сгенерировать более плотный потоковый граф, а потоковый граф используется для рассуждений о всевозможных общих оптимизациях компилятора. Для компилятора пользовательские аннотации такого рода потрясающие. Компилятор попытается выяснить это, но обычно не может (рассматриваемая функция может находиться в другом объектном файле, невидимом для компилятора, или транзитивно использовать какую-то функцию, которая не видима), или когда это происходит, существует какое-то тривиальное исключение, которое может быть выдано, о чем вы даже не подозреваете, поэтому он не может неявно пометить его какnoexcept
(например, выделение памяти может вызвать bad_alloc).источник
x = 5
может бросить. Если эта частьtry
блока послужит какой-либо цели, рассуждения не будут иметь места.throw
операторы или другие подобные вещиnew
. Если компилятор не может видеть тело, он должен полагаться на наличие или отсутствиеnoexcept
. Простой доступ к массиву обычно не генерирует исключения (в C ++ нет проверки границ), поэтому нет, доступ к массиву сам по себе не заставит компилятор думать, что функция генерирует исключения. (Доступ вне зоны доступа - UB, а не гарантированное исключение.)throw()
в pre-noexcept C ++noexcept
может значительно улучшить производительность некоторых операций. Это происходит не на уровне генерации машинного кода компилятором, а путем выбора наиболее эффективного алгоритма: как уже упоминалось, этот выбор выполняется с помощью функцииstd::move_if_noexcept
. Например, ростstd::vector
(например, когда мы звонимreserve
) должен обеспечить надежную гарантию безопасности исключений. Если он знает, чтоT
конструктор перемещения не генерирует, он может просто переместить каждый элемент. В противном случае он должен скопировать всеT
s. Это было подробно описано в этом посте .источник
noexcept
к ним операторы присваивания перемещения (если применимо)! Неявно определенные функции-члены перемещенияnoexcept
добавляются к ним автоматически (если применимо).Никогда? Никогда не время? Никогда.
noexcept
предназначен для оптимизации производительности компилятора так же,const
как и для оптимизации производительности компилятора. Почти никогда.noexcept
прежде всего используется, чтобы позволить «вам» определять во время компиляции, может ли функция генерировать исключение. Помните: большинство компиляторов не генерируют специальный код для исключений, если он на самом деле не генерирует что-либо. Поэтому вопросnoexcept
не в том, чтобы давать подсказки компилятору о том, как оптимизировать функцию, а в том, чтобы давать вам советы о том, как использовать функцию.Шаблоны like
move_if_noexcept
обнаружат, определен ли конструктор перемещенияnoexcept
и вернутconst&
вместо&&
типа тип, если это не так. Это способ сказать, что нужно двигаться, если это очень безопасно.В общем, вы должны использовать,
noexcept
когда вы думаете, что это будет действительно полезно . Некоторый код будет использовать разные пути, еслиis_nothrow_constructible
это верно для этого типа. Если вы используете код, который будет делать это, тогда не стесняйтесьnoexcept
подходящих конструкторов.Вкратце: используйте его для конструкторов перемещения и аналогичных конструкций, но не думайте, что вам придется сходить с ума.
источник
move_if_noexcept
не будет возвращать копию, он будет возвращать const lvalue-reference, а не rvalue-reference. В общем, это заставит вызывающего сделать копию вместо перемещения, ноmove_if_noexcept
не делает копию. Иначе отличное объяснение.noexcept
. Так что «никогда» не соответствует действительности.std::vector
написано, чтобы заставить компилятор компилировать другой код. Дело не в том, что компилятор что-то обнаружил; это о том, что пользовательский код обнаруживает что-то.По словам Бьярне ( язык программирования C ++, 4-е издание , стр. 366):
источник
noexcept
каждый раз, за исключением того, что явно хочу позаботиться об исключениях. Давайте будем реальными, большинство исключений настолько невероятны и / или настолько фатальны, что спасение вряд ли разумно или возможно. Например, в приведенном примере, если распределение завершится неудачно, приложение вряд ли сможет продолжать работать правильно.noexcept
это сложно, так как это является частью интерфейса функций. Особенно, если вы пишете библиотеку, ваш клиентский код может зависеть отnoexcept
свойства. Это может быть трудно изменить позже, так как вы можете сломать существующий код. Это может меньше беспокоить, когда вы реализуете код, который используется только вашим приложением.Если у вас есть функция, которая не может генерировать, спросите себя, будет ли она хотеть остаться
noexcept
или это ограничит будущие реализации? Например, вы можете захотеть ввести проверку ошибок недопустимых аргументов, выдавая исключения (например, для модульных тестов), или вы можете зависеть от другого библиотечного кода, который может изменить его спецификацию исключений. В этом случае безопаснее быть консервативным и опуститьnoexcept
.С другой стороны, если вы уверены, что функция никогда не должна генерировать и правильно, что она является частью спецификации, вы должны объявить ее
noexcept
. Однако имейте в виду, что компилятор не сможет обнаружить нарушения,noexcept
если ваша реализация изменится.Есть четыре класса функций, на которых вы должны сосредоточиться, потому что они, вероятно, окажут наибольшее влияние:
noexcept(true)
если вы их не делаетеnoexcept(false)
)Эти функции обычно должны быть
noexcept
, и наиболее вероятно, что реализации библиотеки могут использоватьnoexcept
свойство. Например,std::vector
можно использовать операции перемещения без броска, не жертвуя строгими исключительными гарантиями. В противном случае придется вернуться к копированию элементов (как это было в C ++ 98).Этот вид оптимизации находится на алгоритмическом уровне и не зависит от оптимизации компилятора. Это может оказать значительное влияние, особенно если элементы копируются дорого.
Преимущество в том, что
noexcept
нет спецификаций исключений илиthrow()
в том, что стандарт предоставляет компиляторам больше свободы, когда дело доходит до разматывания стека. Даже в этомthrow()
случае компилятор должен полностью разматывать стек (и он должен делать это в точном обратном порядке конструкций объектов).В
noexcept
случае, с другой стороны, это не требуется, чтобы сделать это. Не требуется, чтобы стек был размотан (но компилятору все еще разрешено это делать). Эта свобода позволяет дополнительно оптимизировать код, поскольку снижает накладные расходы, связанные с возможностью всегда раскручивать стек.Смежный вопрос о noexcept, раскручивании стека и производительности более подробно рассматривается в служебной информации, когда требуется раскрутка стека.
Я также рекомендую книгу Скотта Мейерса «Effective Modern C ++», «Пункт 14: Объявляйте функции без исключения, если они не будут генерировать исключения» для дальнейшего чтения.
источник
throws
ключевое слово вместоnoexcept
отрицательного. Я просто не могу получить некоторые из вариантов дизайна C ++ ...noexcept
потому чтоthrow
был уже взят. Проще говоря,throw
их можно использовать почти так, как вы упомянули, за исключением того, что они испортили его дизайн, так что он стал почти бесполезным - даже вредным. Но мы застряли с этим сейчас, поскольку его устранение было бы серьезным изменением с небольшой выгодой. Такnoexcept
в принципеthrow_v2
.throw
не полезно?throw()
исключения не предоставляет такую же гарантию, какnothrow
?Когда вы говорите «Я знаю, что [они] никогда не сгенерируют», вы имеете в виду, изучая реализацию функции, вы знаете, что функция не сгенерирует. Я думаю, что этот подход наизнанку.
Лучше рассмотреть, может ли функция генерировать исключения, чтобы быть частью дизайна функции: так же важно, как список аргументов, и является ли метод мутатором (...
const
). Объявление о том, что «эта функция никогда не генерирует исключения» является ограничением для реализации. Отказ от этого не означает, что функция может выдавать исключения; это означает, что текущая версия функции и все последующие версии могут выдавать исключения. Это ограничение, которое усложняет реализацию. Но некоторые методы должны иметь ограничение, чтобы быть практически полезными; самое главное, чтобы их можно было вызывать из деструкторов, а также для реализации кода «отката» в методах, которые обеспечивают гарантию сильных исключений.источник