Имеет ли смысл к каждой функции/методу добавлять noexcept?

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


Ответы (1 шт):

Автор решения: sibedir

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 можно пометить методы, которые потенциально участвуют в "тяжёлых" алгоритмах.

→ Ссылка