Имеет ли смысл к каждой функции/методу добавлять noexcept?
Заметил, что функции редко помечают как noexcept
, когда она не генерирует и не обрабатывает исключения. Хотя на мой взгляд, мы должны помочь компилятору и явно указать, что эта функция будет noexcept
, даже если функция не большая, и он сам может ее так пометить. Есть ли смысл объявлять все функции как noexcept
? Как это повлияет на производительность?
Ответы (1 шт):
https://stackoverflow.com/questions/16104057/does-noexcept-improve-performance Лично мне более всего оппонирует ответ от Steve Jessop. Немного перефразировав его, я бы сказал:
Современные компиляторы, независимо от
noexcept
создают код, который оптимизирован так, как будто он не выбрасывает ни каких исключений. А в случае возникновения исключения, обрабатывает его специфическим кодом (возможно не самым оптимальным с точки зрения целевой производительности), который уже и учитывает то, был ли код, бросивший исключение, помеченnoexcept
или нет.
Вот пример всяческих непотребств
#include <iostream>
#include <vector>
#include <exception>
/*
template <typename ...types>
inline void noopt(types ...args) {
__asm {
DONOTHING:
}
}
*/
const int LACK = rand();
template<bool B>
struct noexcept_BOOL {
noexcept_BOOL() noexcept(B) {
//if (!B and LACK > 1000000000) throw std::exception("AHAHA");
};
noexcept_BOOL(const noexcept_BOOL& other) noexcept(B) = default;
noexcept_BOOL(noexcept_BOOL&& other) noexcept(B) = default;
noexcept_BOOL& operator= (const noexcept_BOOL& other) noexcept(B) = default;
noexcept_BOOL& operator= (noexcept_BOOL&& other) noexcept(B) = default;
};
template<typename T>
__declspec(noinline) void foo(size_t N, size_t C)
{
for (size_t c = 0; c < C; ++c) {
std::vector<T> vec1(N);
std::vector<T> vec2;
for (size_t i = 0; i < N; ++i) {
vec2.emplace_back();
//noopt(i);
}
for (size_t i = 0; i < N; ++i) {
std::swap(vec1[i], vec2[i]);
//noopt(i);
}
vec1.swap(vec2);
for (size_t i = 0; i < N; ++i) {
vec1.erase(vec1.begin());
//noopt(i);
}
//noopt(j);
}
}
int main()
{
auto N = (rand() % 2 + 1) * 10000;
auto C = (rand() % 2 + 1) * 100;
try {
auto begin = clock();
foo<noexcept_BOOL<true>>(N, C);
auto end = clock();
std::cout << "noexcept(true) - " << end - begin << "ms" << std::endl;
}
catch (...) {
return 666;
}
try {
auto begin = clock();
foo<noexcept_BOOL<false>>(N, C);
auto end = clock();
std::cout << "noexcept(false) - " << end - begin << "ms" << std::endl;
}
catch (...) {
return 1;
}
std::cout << "\n\npress [Enter]...";
std::cin.get();
}
Результаты замеров для них идентичны. И не удивительно. Ассемблер для обоих случаем - как две капли воды даже на Debug версии. Т.е. поведение использованных методов стандартной библиотеки, по сути, никак не использует информацию о метках noexcept
(кроме того, что помечает сама свои методы как noexcept
в зависимости от шаблонных параметров).
Да, безусловно, если постараться, можно написать код, который точно будет вести себя зависимо от noexcept
. Но сделать это не просто. Достаточно взглянуть на то, что делает компилятор на Releas.
foo<noexcept_BOOL<false>>(N, C);
...
...
00007FF7074211FD call foo<noexcept_BOOL<0> > (07FF707421350h)
Пока всё отлично. false
- это 0
.
А далее?
foo<noexcept_BOOL<true>>(N, C);
...
...
00007FF707421257 call foo<noexcept_BOOL<0> > (07FF707421350h)
!!!!
Мы явно указываем, что передаваемый тип - noexcept_BOOL<true>
. Но он снова вызывает foo
с параметром noexcept_BOOL<0>
. Ему просто наплевать на noexcept
. Для него это абсолютно идентичные типы. Но вот если вызвать исключение, то ситуация немного поменяется. Но не сильно. Ровно на столько, насколько это минимум необходимо.
В общем случае, я сейчас перешёл от правила "писать noexcept
, если это не неправильно", к правилу "писать noexcept
только там, где это необходимо"
(а когда именно это необходимо, я определяю просто. если я не знаю, что что-то необходимо, но это не необходимо. а если я знаю, почему это необходимо, то это точно необходимо))) компиляторы умнее 99,99% программистов. Это факт
Добавлено Если говорить о более конкретных практических советах, то можно дать такой:
Если Вы уверены, что Ваш класс не может иметь неконсистентных состояний, то noexcept
можно пометить методы, которые потенциально участвуют в "тяжёлых" алгоритмах.