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

Уменьшение размера бинарного файла


Давайте рассмотрим следующий пример, в котором комментариями рассказывается, что за код генерируется компилятором:
#include <iostream>

class my_class {
    int i_;

public:
    explicit my_class (int i)
        : i_(i)
    {}

    int get() const {
        return i_;
    }
};

inline int operator+(const my_class& v1, const my_class& v2) {
    return v1.get() + v2.get();
}

int main() {
    int res = 0;
    try {
        // Если при вызове конструктора для var0 произойдет исключение,
        // распечатываем сообщение об ошибке и выставляем res в -1
        my_class var0(10);

        // Если при вызове конструктора для var1 произойдет исключение,
        // то компилятор должен будет вызвать деструктор
        // переменной var0, распечатать сообщение об ошибке
        // и выставить res в -1
        my_class var1(100);

        // Если при сложении произошло исключение,
        // распечатываем сообщение об ошибке и выставляем res в -1
       
        // Вызов деструкторов var1 и var0 произойдет в любом случае,
        // вне зависимости от генерации исключения.
        res = (var1 + var0 > 0 ? 0 : 1);
    } catch (...) {
        std::cerr << "Произошла ошибка";
        res = -1;
    }

    return res;
}

Многовато кода генерируется компилятором, не правда ли? Именно из-за такого разбухания кода, в некоторых крупных корпорациях (не будем тыкать пальцем в Google) при разработке на С++ запрещено использование исключений. Еще одним примером могут послужить правила разработки для GCC начиная с версии 4.8 (да, GCC теперь разрабатывается с использованием С++, см изменения для 4.8).

Давайте модифицируем класс my_class, чтобы он использовал noexcept и посмотрим что может генерировать компилятор:
class my_class {
    int i_;

public:
    explicit my_class (int i) noexcept
        : i_(i)
    {}

    int get() const noexcept {
        return i_;
    }
};

inline int operator+(const my_class& v1, const my_class& v2) noexcept {
    return v1.get() + v2.get();
}

int main() {
    // std::terminate при исключении
    my_class var0(10);

    // std::terminate при исключении
    my_class var1(100);
       
    // std::terminate при исключении
    int res = (var1 + var0 > 0 ? 0 : 1);

    // Вызов деструкторов var1 и var0
    return res;
}


Заметьте, что std::cerr больше не используется, а следовательно компилятор сможет убрать очень большой кусок кода. Но даже без этого, количество сгенерированного кода стало меньше.

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

Вот результаты компиляции различных версий одного класса на GCC 4.7.2:
1) Первоначальный класс, тело main из первого варианта, флаги -std=c++0x -fno-inline -O2: 5280 байт
2) Класс оптимизирований, тело main из второго варианта, флаги -std=c++0x -fno-inline -O2: 4800 байт

3) Класс оптимизирований, тело main из первого варианта, флаги -std=c++0x -fno-inline -O2: 5280 байт
4) Класс оптимизирований, тело main из первого варианта без iostream, флаги -std=c++0x -fno-inline -O2: 4800 байт
5) Класс оптимизирований, тело main из второго варианта + iostream включается, флаги -std=c++0x -fno-inline -O2: 5280 байт

* Флаг -std=c++0x включает возможности C++11, который необходим для использования noexcept.
** При повторении эксперимента, не забудьте убрать отладочную информацию из бинарного файла.

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

Немного схитрим, и разнесем метод main() и имплементацию my_class по разным файлам, чтобы компилятору было сложнее оптимизировать.
Результаты:
Первоначальный класс, тело main из первого варианта, флаги -std=c++0x -fno-inline -O2: 6952 байт
Класс оптимизирований, тело main из первого варианта, флаги -std=c++0x -fno-inline -O2: 5288 байт

Итого: выигрыш в размере в 23.9% после добавления трех noexcept.

Ускорение работы стандартных алгоритмов


Для многих людей, использующих С++11 может стать большой неожиданностью данная часть стандарта N3050 (на данный момент этот стандарт реализован не во всех ведущих компиляторах). Если в кратце, в ней говорится, что стандартные алгоритмы и контейнеры не должны использовать move assignment и move construction если эти методы могут кидать исключения. Чем это может грозить рассмотрим на примере:
// Класс с ресурсоёмким копированием и быстрым move assignment
class move_fast_copy_slow {
    // Некие члены класса объявлены здесь
public:
    move_fast_copy_slow(move_fast_copy_slow&&);         // 1
    move_fast_copy_slow(const move_fast_copy_slow&);    // 2
   
    move_fast_copy_slow& operator=(move_fast_copy_slow&&);      // 3
    move_fast_copy_slow& operator=(const move_fast_copy_slow&); // 4
};


Если методы 1 и 3 не пометить noexcept, то стандартные контейнеры будут использовать более медленные методы 2 и 4. Вследствие этого работа с контейнерами std::vector и std::deque может замедлиться на пару порядоков. При том, данное замедление коснется и всех типов наследуемых или использующих move_fast_copy_slow в качестве члена.

Совместимость


На момент написания данной статьи не все ведущие компиляторы поддерживают noexcept. Используйте макрос BOOST_NOEXCEPT из библиотеки boost вместо noexcept для переносимости кода.

Подводные камни


По стандарту, noexcept не является частью типа функции, однако при использовании виртуальных ф��нкций все перегруженные функции должны иметь такую же либо более строгую спецификацию исключений. То есть следующий код не соберется:
class base {
public:
    virtual void foo() noexcept {}
};

class derived: public base {
public:
    void foo() override { throw 1; }
};

int main () {
    derived d;
    base *p = &d;
    p->foo();
}

Будет ошибка типа «error: looser throw specifier for ‘virtual void derived::foo()’».
Так что будьте готовы к тому, что если вы автор библиотеки и добавили спецификацию noexcept к фиртуальной функции, то у пользователей вашей библиотеки перестанет собираться код.
Так же приготовьтесь к тому, что ваши классы исключений c перегруженным методом what() и наследуемые от стандартных, могут не компилироваться если они не помечены noexcept:
class my_exception : public std::exception {
    const char* what() throw() { // В С++11 должно быть noexcept вместо throw()
        return "my_exception::what()";
    }
};

Заключение


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