Имеет ли смысл объявлять встроенные функции noexcept?
Из того, что я могу сказать, сообщество SO делится на то, объявляет ли функция noexcept
значимую оптимизацию компилятора, которая в противном случае была бы невозможна. (Я говорю конкретно об оптимизации компилятора, а не об оптимизации реализации библиотеки на основе move_if_noexcept
.) Для целей этого вопроса предположим, что noexcept
делает возможной оптимизацию генерации кода. С этим допущением имеет смысл объявить inline
функции noexcept
? Предполагая, что такие функции фактически встроены, это, по-видимому, требует, чтобы компиляторы генерировали эквивалент блока try
вокруг кода, полученного в результате функции inline
на сайте вызова, потому что, если в этой области возникает исключение, terminate
должен быть вызван. Без noexcept
этот блок try
кажется ненужным.
Мой первоначальный интерес заключался в том, было ли смысл объявлять функции лямбда noexcept
, учитывая, что они неявно inline
, но потом я понял, что одни и те же проблемы возникают для любой функции inline
, а не только для Lambdas.
Ответы
Ответ 1
допустим, что noexcept действительно делает возможной оптимизацию генерации кода
OK
Предполагая, что такие функции действительно включены, это, казалось бы, требуют, чтобы компиляторы генерировали эквивалент блока try вокруг код, полученный в результате встроенной функции на сайте вызова, потому что если в этой области возникает исключение
Не обязательно, потому что может быть, что компилятор может смотреть на тело функции и видеть, что он ничего не может выбросить. Поэтому номинальная обработка исключений может быть отменена.
Если функция "полностью" встроена (то есть, если встроенный код не содержит вызовов функций), я бы ожидал, что компилятор может довольно часто делать это определение, но не, например, в случае, когда есть вызов до vector::push_back()
, и писатель функции знает, что зарезервировано достаточное пространство, но компилятор этого не делает.
Помните также, что в хорошей реализации блок try может фактически не потребовать выполнения какого-либо кода в случае, когда ничего не выбрано.
С этим предположением имеет смысл объявлять встроенные функции noexcept?
Да, чтобы получить все предполагаемые оптимизации noexcept
.
Ответ 2
Стоит отметить, что в кругах власти была интересная дискуссия о проблемах, не связанных с норой. Я настоятельно рекомендую прочитать их:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3227.html
http://www.stroustrup.com/N3202-noexcept.pdf
По-видимому, весьма влиятельные люди заинтересованы в добавлении своего рода автоматического вывода nothrow в С++.
После некоторого размышления я изменил свое положение почти на противоположное, ниже
Рассмотрим это:
-
когда вы вызываете функцию, которая имеет noexcept
в объявлении, вам это выгодно (не нужно иметь дело с возможностью разворота и т.д.)
-
когда компилятор компилирует функцию, которая имеет noexcept
on definion - (если компилятор не может доказать, что функция действительно незначительная) производительность страдает (теперь компилятор должен гарантировать, что исключение не сможет избежать этой функции). Вы просите его обеспечить соблюдение обещаний без исключений.
т.е. noexcept
оба причиняют вам боль и приносят пользу. Это не так, если функция встроена! Когда он встроен - нет никакой пользы от noexcept от объявления вообще (декларация и определение становятся одной вещью). То есть, если вы на самом деле не хотите, чтобы компилятор обеспечивал это ради безопасности. И по безопасности я имею в виду, что вы скорее закончите, чем произведете неправильный результат.
Теперь должно быть очевидно - нет смысла объявлять встроенные функции noexcept
(имейте в виду, что не каждая встроенная функция будет входить в нее).
Давайте посмотрим на разные категории функций, которые не выбрасывают (вы просто знаете, что они этого не делают):
-
non-inlined, компилятор может доказать, что он не бросает - noexcept
не повредит тело функции (компилятор просто проигнорирует спецификацию), и сайты-вызовы выиграют от этого обещания
-
non-inlined, компилятор не может доказать, что он не выбрасывает - noexcept
может повредить тело функции, но выгодные сайты вызова (трудно сказать, что более выгодно)
-
inlined, компилятор может доказать, что он не бросает - noexcept
не служит цели
-
inlined, компилятор не может доказать, что он не бросает - noexcept
повредит сайт вызова
Как вы видите, nothrow
- это просто плохо разработанная языковая функция. Он работает только в том случае, если вы хотите выполнить обещание без исключений. Нельзя использовать его правильно - он может дать вам "безопасность", но не производительность.
noexcept
ключевое слово в конечном итоге используется как как обещание (при объявлении), так и принудительное исполнение (по определению) - не идеальный подход, я думаю (lol, второй удар по спецификациям исключений, и мы все еще не поняли его).
Итак, что делать?
-
объявить о своем поведении (увы, язык не может вам помочь)! Например:.
void push_back(int k); // throws only if there is no unused memory
-
не ставьте noexcept
на встроенные функции (если это маловероятно, чтобы быть встроенным, например очень большим)
-
для не-встроенных функций (или функция, которая вряд ли будет встроена) - вызов. Чем больше функция получает меньший noexcept
отрицательный эффект становится (сравнительно) - в какой-то момент, вероятно, имеет смысл указать его для выгоды от звонящих
-
используйте noexcept
для перемещения конструктора и переместите оператор присваивания (и деструктор?). Это может повлиять на них отрицательно, но если вы этого не сделаете - некоторые функции библиотеки (std:: swap, некоторые операции с контейнерами) не будут использовать наиболее эффективный путь (или не будут предоставлять лучшую гарантию исключения). В принципе, любое место, использующее noexcept
оператора для вашей функции (на данный момент), заставит вас использовать noexcept
спецификатор.
-
используйте noexcept
, если вы не доверяете вызовам, которые выполняет ваша функция, и скорее умрут, чем они себя ведут неожиданно
-
чистые виртуальные функции - чаще всего вы не доверяете людям, реализующим эти интерфейсы. Часто это имеет смысл покупать страхование (указав noexcept
)
Ну, как иначе noexcept
можно было бы спроектировать?
-
Я бы использовал два разных ключевых слова - один для объявления обещания, а другой для его принудительного применения. Например. noexcept и force_noexcept. Фактически, принудительное выполнение на самом деле не требуется - это можно сделать с помощью try/catch + terminate() (да, он разворачивает стек, но кто волнуется, если за ним следует std:: terminate()?)
-
Я бы заставил компилятор проанализировать все вызовы в заданной функции, чтобы определить, может ли он бросить. Если это так, и было сделано безусловное обещание - ошибка компилятора будет выпущена
-
Для кода, который может быть брошен, но вы знаете, что этого не должно быть, чтобы обеспечить компилятор, чтобы он был в порядке. Smth вот так:
vector<int> v;
v.reserve(1);
...
nothrow { // memory pre-allocated, this won't throw
v.push_back(10);
}
если обещание нарушено (т.е. кто-то изменил векторный код и теперь он предоставляет другие гарантии) - поведение undefined.
Отказ от ответственности: этот подход может быть слишком непрактичным, кто знает...