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

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

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

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

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

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


person void-pointer    schedule 28.05.2012    source источник
comment
Код, который использует move_if_nothrow (или whatchamacallit), увидит улучшение производительности, если не будет другого аргумента перемещения.   -  person R. Martinho Fernandes    schedule 28.05.2012
comment
По теме: github.com/isocpp/CppCoreGuidelines/blob/master/   -  person moooeeeep    schedule 27.03.2018
comment
std :: move_if_noexcept   -  person TamaMcGlinn    schedule 23.02.2021


Ответы (9)


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

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

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

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

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

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

person Pubby    schedule 28.05.2012
comment
Я думаю, что трюк std::terminate все еще подчиняется модели нулевой стоимости. То есть так получилось, что диапазон инструкций в noexcept функциях отображается на вызов std::terminate, если throw используется вместо разматывателя стека. Поэтому я сомневаюсь, что у него больше накладных расходов, чем у обычного отслеживания исключений. - person Matthieu M.; 28.05.2012
comment
Например, std :: vector не будет использовать перемещение вашего класса, если он не отмечен как noexcept. Какие? Действительно? Вы уверены, что это необходимо? - person Klaim; 29.05.2012
comment
@Klaim См. Это: stackoverflow.com/a/10128180/964135 На самом деле он просто должен быть без метания, но noexcept гарантирует это. - person Pubby; 29.05.2012
comment
@Pubby Хорошо, спасибо за разъяснения, это довольно интересная тонкость. Это объясняет, почему мой код все еще движется. - person Klaim; 29.05.2012
comment
Если функция noexcept выдает ошибку, то вызывается std::terminate, что, похоже, потребует небольших накладных расходов ... Нет, это должно быть реализовано, не создавая таблицы исключений для такой функции, которую диспетчер исключений должен уловить, а затем выручить. - person Potatoswatter; 29.05.2012
comment
@Potatoswatter Да, я думаю, что я пытался сказать, что это не удалит полностью обработку исключений. Есть предложение, на что мне следует изменить это предложение в сообщении? - person Pubby; 29.05.2012
comment
@Pubby Обработка исключений C ++ обычно выполняется без накладных расходов, за исключением таблиц переходов, которые сопоставляют адреса потенциально вызывающих сайтов с точками входа обработчика. Удаление этих таблиц максимально приближено к полному удалению обработки исключений. Единственное отличие - размер исполняемого файла. Наверное, не о чем и говорить. - person Potatoswatter; 29.05.2012
comment
Почему они не используют noexcept в качестве аргумента по умолчанию? - person bartolo-otrit; 29.04.2013
comment
Хорошо, тогда используйте его, когда очевидно, что функция никогда не выдаст. Я не согласен. noexcept является частью интерфейса функции; вам не следует добавлять его только потому, что ваша текущая реализация не выбрасывает. Я не уверен в правильном ответе на этот вопрос, но совершенно уверен, что то, как ваша функция ведет себя сегодня, не имеет к этому никакого отношения ... - person Nemo; 06.11.2014
comment
@Potatoswatter: обработка исключений C ++ обычно выполняется без накладных расходов, за исключением таблиц переходов. Некоторые авторы компиляторов утверждают, что это не так; например найдите Chandler на странице meetingcpp.com/index. php / br / items / insights-into-new-and-c.html. Компилятор, зная, что поток управления линейен, безусловно, может позволить некоторые оптимизации, по крайней мере, в принципе. - person Nemo; 06.11.2014
comment
@Nemo См. Также мой недавний ответ, stackoverflow .com / questions / 26079903 / - person Potatoswatter; 06.11.2014
comment
@Nemo Любая возможная оптимизация без исключения все же возможна с исключениями, это очевидное наблюдение. - person curiousguy; 20.05.2018
comment
@curiousguy: Неправильно. Если вы что-то знаете об оптимизации компилятора, придумывать примеры банально ... Но я просто отсылаю вас к akrzemi1.wordpress.com/2014/04/24/noexcept-what-for и предлагаю вам прочитать еще что-нибудь самостоятельно. - person Nemo; 22.05.2018
comment
@Nemo Справочник не описывает оптимизацию компилятора. - person curiousguy; 23.05.2018
comment
Достаточно ли важна эта функция для создания ключевого слова? - person ar2015; 23.09.2018
comment
еще рано? - person HAL9000; 22.09.2020
comment
Что такое большая четверка? В вашем ответе я вижу только 3: конструкторы, присваивание, а не деструкторы. Кто-нибудь может уточнить? - person NXH; 22.10.2020
comment
@NamHoang: 1. Копировать конструктор 2. Копировать присваивание 3. Переместить конструктор 4. Переместить присваивание - person Ben Voigt; 31.03.2021

Как я не перестаю повторять в последнее время: прежде всего семантика.

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

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


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

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

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

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

  • сразу же извлекает выгоду из noexcept, даже если зависимости его еще не используют (обратная совместимость)
  • разрешение указания noexcept при вызове функций, которые теоретически могут вызывать, но не ожидаются для данных аргументов
person Matthieu M.    schedule 28.05.2012
comment
Возможно, я наивен, но я мог бы представить, что функция, вызывающая только noexcept функции, не должна делать ничего особенного, потому что любые исключения, которые могут возникнуть, запускают terminate прежде, чем они достигнут этого уровня. Это сильно отличается от необходимости иметь дело с bad_alloc исключением и распространять его. - person ; 28.05.2012
comment
@Hurkyl: ты прав. Вот почему достаточно умный компилятор мог бы это понять и избежать создания записей в таблице счетчика программ / обработчика исключений для этой функции (таким образом уменьшая жир, как я сказал). Однако многие функции не будут помечены noexcept (подумайте, например, о системных вызовах или низкоуровневых функциях C, специфичных для ОС), поэтому было выбрано сокращение, позволяющее компилятору не проверять его. - person Matthieu M.; 28.05.2012
comment
Да, можно определить noexcept так, как вы предлагаете, но это было бы действительно бесполезной функцией. Многие функции могут вызывать, если не выполняются определенные условия, и вы не можете их вызвать, даже если знаете, что условия выполняются. Например, любая функция, которая может генерировать std :: invalid_argument. - person tr3w; 24.04.2013
comment
@ tr3w: Я понимаю ваш аргумент, однако я буду возражать против реальной практики = ›так легко неправильно понять требование и получить функцию throw, что не должно быть разрешено быть в noexcept функции (без надлежащего try / catch по крайней мере ). И даже если сейчас он работает, любое количество рефакторинга / обслуживания может сделать это позже недействительным. Это самое большое преимущество проверок во время компиляции: они являются исчерпывающими и содержат версию за версией. - person Matthieu M.; 24.04.2013
comment
Поскольку изменения компилятора часто требуют, чтобы все было перестроено в любом случае, почему бы не использовать изменение имени, чтобы объявление метода foo_whatever как noexcept определило бы как foo_whatever, так и __noex__foo_whatever, а определение метода без noexcept просто определило бы первый. Если noexcept методы вызывают foo, компилятор должен вызвать __noex_foo_whatever, а также сгенерировать слабое определение для __noex_foo_whatever, которое вызывает foo_whatever и завершает работу, если генерирует исключение? Тогда noexcept, который вызывает только другие noexcept методы, может избежать всех накладных расходов. - person supercat; 13.08.2013
comment
@MatthieuM. Немного поздно для ответа, но тем не менее. Функции, помеченные как noexcept, могут вызывать другие функции, которые могут генерировать сообщения, обещание состоит в том, что эта функция не будет генерировать исключение, т.е. они просто должны сами обработать исключение! - person Honf; 26.12.2013
comment
Или передайте аргументы, которые гарантируют, что вызываемый объект не будет бросать, даже если он это делает для других входных данных - что-то, что компиляторы недостаточно умны или не имеют достаточно информации для проверки, но программисты в хороший день имеют и делают. Вы не всегда хотите, чтобы вам приходилось писать версии своих функций except и noexcept в зависимости от того, проверил ли вызывающий каким-либо образом входные данные заранее, например operator[] vs. at(). - person Steve Jessop; 24.01.2014
comment
Я проголосовал за этот ответ давным-давно, но, прочитав и подумав еще немного, у меня есть комментарий / вопрос. Семантика перемещения - это единственный пример, который я когда-либо видел, когда кто-то давал, где noexcept явно полезен / хорошая идея. Я начинаю думать, что конструкция перемещения, назначение перемещения и своп - единственные случаи ... Вы знаете какие-нибудь другие? - person Nemo; 25.12.2014
comment
@Nemo: В стандартной библиотеке он, возможно, единственный, однако он демонстрирует принцип, который можно повторно использовать в другом месте. Операция перемещения - это операция, которая временно помещает какое-то состояние в неопределенное состояние, и только когда оно noexcept, кто-то может уверенно использовать его для части данных, к которой можно будет получить доступ впоследствии. Я мог видеть, что эта идея используется в другом месте, но стандартная библиотека в C ++ довольно тонкая и, как мне кажется, используется только для оптимизации копий элементов. - person Matthieu M.; 26.12.2014

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

person Terry Mahaffey    schedule 28.05.2012
comment
Действительно ли это имеет значение на практике? Пример надуманный, потому что ничто до x = 5 не может бросить. Если бы эта часть try блока служила какой-либо цели, рассуждения не выдержали бы. - person Potatoswatter; 29.05.2012
comment
Я бы сказал, что это действительно имеет значение для оптимизации функций, содержащих блоки try / catch. Приведенный мной пример, хотя и надуманный, не является исчерпывающим. Важнее то, что noexcept (например, оператор throw () перед ним) помогает компиляции сгенерировать меньший потоковый граф (меньше ребер, меньше блоков), что является фундаментальной частью многих оптимизаций, которые он выполняет. - person Terry Mahaffey; 29.05.2012
comment
Как компилятор может распознать, что код может генерировать исключение? Считается ли и доступ к массиву возможным исключением? - person Tomas Kubes; 20.04.2015
comment
@ qub1n Если компилятор видит тело функции, он может искать явные инструкции throw или другие вещи, такие как new, которые могут вызывать. Если компилятор не видит тело, он должен полагаться на наличие или отсутствие noexcept. Обычный доступ к массиву обычно не создает исключений (C ++ не имеет проверки границ), поэтому нет, доступ к массиву сам по себе не заставит компилятор думать, что функция генерирует исключения. (Доступ за пределами границы - это UB, а не гарантированное исключение.) - person cdhowie; 16.07.2015
comment
@cdhowie он должен полагаться на наличие или отсутствие noexcept или наличие throw() в pre-noexcept C ++ - person curiousguy; 28.05.2018
comment
@TerryMahaffey реальная разница в функциях оптимизации, которые содержат блоки try / catch ... которые полностью обрабатывают исключение, а не блоки типа Java, которые повторно генерируют, что составляет 99% от try / catch в коде библиотеки - person curiousguy; 28.05.2018

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

person Andrzej    schedule 24.09.2012
comment
Дополнение: это означает, что если вы определяете конструкторы перемещения или операторы присваивания перемещения, добавьте к ним noexcept (если применимо)! Неявно определенные функции-члены перемещения noexcept добавляются к ним автоматически (если применимо). - person mucaho; 03.11.2015

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

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

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

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

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

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

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

person Nicol Bolas    schedule 28.05.2012
comment
Строго говоря, move_if_noexcept не будет возвращать копию, он вернет const lvalue-reference, а не rvalue-reference. Обычно это заставляет вызывающего абонента делать копию вместо перемещения, но move_if_noexcept не делает копию. В противном случае отличное объяснение. - person Jonathan Wakely; 28.05.2012
comment
+1 Джонатан. Например, изменение размера вектора приведет к перемещению объектов вместо их копирования, если конструктор перемещения - noexcept. Так что это никогда не неправда. - person mfontanini; 28.05.2012
comment
@mfontanini: Это не оптимизация для компилятора, о чем он и спрашивал. - person Nicol Bolas; 28.05.2012
comment
Я имею в виду, что в этой ситуации компилятор сгенерирует лучший код. OP запрашивает пример, для которого компилятор может создать более оптимизированное приложение. Кажется, это так (хотя это и не оптимизация для компилятора). - person mfontanini; 28.05.2012
comment
@mfontanini: компилятор генерирует лучший код только потому, что компилятор вынужден компилировать другой путь кода. Это только работает, потому что std::vector написано, чтобы заставить компилятор компилировать другой код. Дело не в том, что компилятор что-то обнаруживает; речь идет о пользовательском коде, который что-то обнаруживает. - person Nicol Bolas; 28.05.2012
comment
приведите пример кода, для которого компилятор C ++ может генерировать лучший машинный код после добавления noexcept - ›Я знаю, что компилятор не виноват в том, что приложение работает более эффективно, но компилятор все-таки генерирует его. - person mfontanini; 28.05.2012
comment
А поскольку библиотека является определенной частью стандарта C ++, мне все равно, компилятор это или библиотека, пока пометка моего конструктора перемещения noexcept дает более быстрый код во всех реализациях. - person Christian Rau; 28.05.2012
comment
@mfontanini: все-таки компилятор его генерирует. По этой логике использование лучшего алгоритма является оптимизацией компилятора. Anything - это оптимизация компилятора. Таким образом, термин не имеет полезного значения. - person Nicol Bolas; 28.05.2012
comment
Дело в том, что я не могу найти оптимизацию компилятора в цитате в начале вашего ответа. Как сказал @ChristianRau, если компилятор генерирует более эффективный код, не имеет значения, какова причина этой оптимизации. В конце концов, компилятор генерирует более эффективный код, не так ли? PS: Я никогда не говорил, что это оптимизация компилятора, я даже сказал, что это не оптимизация компилятора. - person mfontanini; 29.05.2012
comment
@NicolBolas Я чувствую, что вы действительно спорите здесь о семантике. Я знаю, что это не совсем компилятор, который генерирует лучший код. Я также знаю, что вы хотите сказать концептуально, и что аргументированная семантика - не такая уж и плохая вещь в C ++. Но давайте будем честными, move_if_noexcept - это именно именно тот материал, о котором идет речь. Если это строго компилятор или библиотека, не имеет значения, поскольку с практической точки зрения они одинаковы в этом сценарии для любого пользователя кто на самом деле не разрабатывает стандартную библиотеку C ++, что почти никто не делает. - person Christian Rau; 14.03.2017

Выражаясь словами Бьярна (Язык программирования C ++, 4-е издание, стр. 366):

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

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

person Saurav Sahu    schedule 09.01.2017
comment
Для меня это звучит так, как будто я действительно должен каждый раз добавлять noexcept, за исключением того, что я явно хочу позаботиться об исключениях. Давайте будем честными, большинство исключений настолько маловероятны и / или фатальны, что спасение вряд ли разумно или возможно. Например. в приведенном примере, если распределение не удалось, приложение вряд ли сможет продолжить работу правильно. - person Neonit; 08.08.2019
comment
Это может быть наивный вопрос, но почему акцент делается на vector<double> tmp(10);? Разве создание экземпляра строки в строке выше не может с одинаковым успехом выдавать, если для этого недостаточно памяти? - person Daniel Daranas; 12.04.2021

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

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

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

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

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

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

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

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

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

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

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

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

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

Я также рекомендую книгу Скотта Мейерса «Эффективный современный C ++», «Правило 14: объявляйте функции без исключения, если они не будут генерировать исключения» для дальнейшего чтения.

person Philipp Claßen    schedule 25.12.2014
comment
Тем не менее, было бы гораздо больше смысла, если бы исключения были реализованы в C ++, как в Java, где вы отмечаете метод, который может генерировать ключевое слово throws вместо noexcept отрицательного. Я просто не могу получить некоторые варианты дизайна C ++ ... - person doc; 06.11.2015
comment
Они назвали его noexcept, потому что throw уже был занят. Проще говоря, throw можно использовать почти, как вы упомянули, за исключением того, что они испортили его дизайн, поэтому он стал почти бесполезным - даже вредным. Но сейчас мы застряли в нем, так как его удаление было бы критическим изменением с небольшой пользой. Итак, noexcept в основном throw_v2. - person AnorZaken; 11.07.2016
comment
Чем throw бесполезен? - person curiousguy; 23.05.2018
comment
Сам @curiousguy throw (для генерации исключений) полезен, но throw как спецификатор исключения устарел, а в C ++ 17 даже удален. По причинам, по которым спецификаторы исключения бесполезны, см. Этот вопрос: stackoverflow.com/questions/88573/ - person Philipp Claßen; 23.05.2018
comment
@ PhilippClaßen Спецификатор исключения throw() не обеспечивает такой же гарантии, как nothrow? - person curiousguy; 23.05.2018
comment
@curiousguy Да, есть разница, которая актуальна для оптимизации компилятора. Если вы действительно выбрасываете, throw() принудительно разматывает стек, тогда как C ++ 11 noexcept оставляет его открытым для реализации. Это меньше ограничений для оптимизатора. См. Этот вопрос, который также содержит цитату из книги Скотта Мейера, о которой я говорю: stackoverflow.com/q/26079903/783510 - person Philipp Claßen; 24.05.2018
comment
Эта свобода позволяет дальнейшую оптимизацию кода, поскольку снижает накладные расходы, связанные с возможностью всегда раскручивать стек. Это не должно иметь значения при реализации с нулевыми накладными расходами. - person curiousguy; 27.05.2018
comment
Но даже для раскрутки стека, когда вы не бросаете, компилятор ограничен. Он дополнительно ограничен соглашениями о вызовах (часть ABI). Для компиляторов непростая задача реализовать текущую модель исключений без каких-либо накладных расходов. - person Philipp Claßen; 24.10.2019

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

Когда вы говорите: «Я знаю, что [они] никогда не выбросят», вы имеете в виду, исследуя реализацию функции, что вы знаете, что функция не выбросит. Я думаю, что этот подход вывернут наизнанку.

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

person Raedwald    schedule 29.05.2012
comment
На сегодняшний день это лучший ответ. Вы даете гарантию пользователям вашего метода, что является еще одним способом сказать, что вы ограничиваете свою реализацию навсегда (без нарушения-изменения). Спасибо за поучительную перспективу. - person AnorZaken; 11.07.2016
comment
См. Также связанный вопрос о Java. - person Raedwald; 04.05.2018

Вот простой пример, чтобы проиллюстрировать, когда это действительно может иметь значение.

#include <iostream>
#include <vector>
using namespace std;
class A{
 public:
  A(int){cout << "A(int)" << endl;}
  A(const A&){cout << "A(const A&)" << endl;}
  A(const A&&) noexcept {cout << "A(const A&&)" << endl;}
  ~A(){cout << "~S()" << endl;}
};
int main() {
  vector<A> a;
  cout << a.capacity() << endl;
  a.emplace_back(1);
  cout << a.capacity() << endl;
  a.emplace_back(2);
  cout << a.capacity() << endl;
  return 0;
}

Вот результат

0
A(int)
1
A(int)
A(const A&&)
~S()
2
~S()
~S()

Если мы удалим noexcept в конструкторе перемещения, вот результат

0
A(int)
1
A(int)
A(const A&)
~S()
2
~S()
~S()

Ключевое отличие - A(const A&&) против A(const A&&). Во втором случае он должен скопировать все значения с помощью конструктора копирования. ОЧЕНЬ НЕЭФФЕКТИВНО !!

person drbombe    schedule 17.04.2021