Что такое спецификатор noexcept в C++?

Ответ

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

1. Синтаксис и использование:

// 1. Функция, которая не бросает исключения
void my_function() noexcept {
    // ... код без throw
}

// 2. Условный noexcept. Функция noexcept, если выражение в скобках истинно.
//    Например, перемещающий конструктор может быть noexcept, если перемещение
//    всех членов класса не бросает исключений.
template<typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b))) {
    a.swap(b);
}

// 3. Оператор noexcept. Возвращает true, если выражение не генерирует исключений.
bool will_throw = noexcept(throw std::runtime_error("")); // false
bool wont_throw = noexcept(1 + 2); // true

2. Ключевые причины для использования noexcept:

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

  • Гарантии безопасности исключений в STL: Многие алгоритмы и контейнеры стандартной библиотеки используют перемещающие операции (конструктор перемещения, оператор присваивания перемещением) только если они объявлены как noexcept. В противном случае, для обеспечения строгой гарантии безопасности исключений, библиотека может откатиться к более медленному копированию.

    class MyType {
        std::vector<int> data;
    public:
        // Перемещающий конструктор с noexcept — критически важен для эффективности
        MyType(MyType&& other) noexcept : data(std::move(other.data)) {}
        // ...
    };

    При std::vector::resize, если перемещающий конструктор noexcept, элементы будут перемещены. Если нет — скопированы.

  • Улучшение читаемости интерфейса: Явно указывает на намерение разработчика и контракт функции.

3. Важные правила:

  • Деструкторы по умолчанию неявно объявлены как noexcept.
  • Все функции стандартной библиотеки, которые не предназначены для генерации исключений, помечены как noexcept.
  • noexcept является частью типа функции. Указатели на функции с noexcept и без него — разные типы.
  • Не стоит бездумно помечать все функции как noexcept. Это должно быть осознанное решение, основанное на реализации функции и операций, которые она вызывает.

Ответ 18+ 🔞

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

Представь, ты пишешь функцию и клянёшься всем святым: «Эта штука НИКОГДА не выкинет исключение, ёпта!». Вот ты эту клятву и оформляешь в коде словечком noexcept. Если ты соврал, и исключение всё-таки вылетело — программа просто хлопнется нахуй, вызвав std::terminate(). Всё. Никаких тебе try-catch, никаких шансов на спасение. Доверия к тебе, блядь, ноль, вот и расплата.

1. Как это выглядит в деле:

// 1. Просто и честно: "Не брошу!"
void моя_чистая_функция() noexcept {
    // ... тут код, который не швыряется исключениями
}

// 2. Хитрая жопа: "Не брошу, но только если вот это условие выполнится".
//    Часто используется для перемещающих операций.
template<typename T>
void поменять_местами(T& a, T& b) noexcept(noexcept(a.swap(b))) {
    a.swap(b); // noexcept только если сам swap у 'a' — noexcept
}

// 3. Оператор-детектив: "А бросит ли вот это выражение исключение?"
bool бросит_ли = noexcept(throw std::runtime_error("ой")); // false — ну очевидно же
bool не_бросит = noexcept(1 + 2); // true — сложение целых чисел на исключения не тянет

2. Зачем вообще этот геморрой?

  • Оптимизация, ёбанаврот! Компилятор — тот ещё параноик. Если функция не noexcept, он вынужден готовить на всякий пожарный целый ворох кода для раскрутки стека, если вдруг исключение вылетит. А если ты дал клятву noexcept, он расслабляется и генерит код более компактный и быстрый. Разница может быть как хуй с горы, так и вполне заметной.

  • Любовь стандартной библиотеки (STL). Вот тут самое важное. STL — она не просто так, она хитрая. Когда ей нужно перетасовать элементы в памяти (например, внутри vector при resize), она смотрит: а можно ли их БЕЗОПАСНО переместить? Безопасно — это значит, что перемещающий конструктор твоего класса объявлен как noexcept. Если да — она их просто переносит, быстро и эффективно. Если нет — она, блядь, откатывается к старому доброму и медленному копированию, потому что боится, что перемещение сломается на полпути и всё придётся откатывать.

    class МойКласс {
        std::vector<int> данные;
    public:
        // Вот этот самый важный чувак. Без noexcept — будет копирование и тормоза.
        МойКласс(МойКласс&& другой) noexcept : данные(std::move(другой.данные)) {}
        // ...
    };

    Честно, это главная причина, по которой noexcept для перемещающих операций — это не прихоть, а must have. Иначе твой код будет бздеть как старый дед.

  • Читаемость, наконец. Глядя на сигнатуру, сразу ясно: эту функцию можно спокойно использовать в критических местах, где исключения недопустимы. Это как знак качества, ёб твою мать.

3. Что важно помнить, чтобы не облажаться:

  • Деструкторы по умолчанию уже noexcept. И слава богу, а то представляешь, если бы они начинали швыряться исключениями при очистке? Пиздец, а не программа.
  • Весь код STL, который по смыслу не должен кидаться исключениями, помечен noexcept. Это не просто так.
  • noexcept — это часть типа функции. Указатель на функцию с noexcept и без — это два разных пидараса, они друг другу не подходят.
  • НЕ НАДО помечать всё подряд как noexcept просто потому, что модно. Это как давать клятву «не пить», стоя на вечеринке. Если внутри функции вызывается что-то, что может бросить исключение, а ты навесил noexcept — ты подписал себе и своей программе приговор. Сам от себя охуеешь, когда она внезапно рухнет без объяснения причин. Используй с умом, чувак.