← Назад к вопросам

Что такое noexcept и когда его использовать?

2.0 Middle🔥 211 комментариев
#Исключения и обработка ошибок#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое noexcept и когда его использовать?

Определение

noexcept — это спецификатор в C++11 и позже, который явно указывает, что функция не бросает исключения. Это обещание компилятору и программистам, что данная функция работает в режиме "no exception guarantee".

Синтаксис

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

// Форма 2: С условием (runtime evaluation)
bool mightThrow(const std::vector<int>& vec) noexcept(false) {
    // может бросить
}

// Форма 3: Зависит от других функций
template<typename T>
void process(T value) noexcept(noexcept(T::cleanup())) {
    // noexcept если cleanup() не бросает
}

Чем это помогает компилятору?

Без noexcept:

void processData(std::vector<int>& v) {
    v.push_back(42);
    // Компилятор не знает, может ли push_back() бросить исключение
    // Может ли раскинуться try-catch код?
}

С noexcept:

void processData(std::vector<int>& v) noexcept {
    // Компилятор ЗНАЕТ, что исключений не будет
    // Может генерировать более эффективный код
    // Может пропустить try-catch обработку в вызывающем коде
}

Оптимизация: move semantics

Самый важный кейс для noexcept — это move операции:

class DataContainer {
private:
    std::vector<int> data_;
public:
    // move assignment БЕЗ noexcept — небезопасно
    DataContainer& operator=(DataContainer&& other) {
        data_ = std::move(other.data_);
        return *this;
    }
    
    // move assignment С noexcept — может использовать std::move
    DataContainer& operator=(DataContainer&& other) noexcept {
        data_ = std::move(other.data_);
        return *this;
    }
};

// Пример где это критично:
std::vector<DataContainer> containers;
containers.push_back(DataContainer()); // реаллокация

// Если move assignment БЕЗ noexcept:
// → vector НЕ может использовать move для переноса элементов
// → вынужден использовать copy (медленно!)

// Если move assignment С noexcept:
// → vector может безопасно использовать move
// → очень быстро!

Где использовать noexcept

1. Move операции (обязательно)

class MyClass {
public:
    // Move constructor
    MyClass(MyClass&& other) noexcept 
        : data_(std::move(other.data_)) {}
    
    // Move assignment
    MyClass& operator=(MyClass&& other) noexcept {
        data_ = std::move(other.data_);
        return *this;
    }
    
    // Destructor (обычно не выбрасывает)
    ~MyClass() noexcept {}
    
private:
    std::unique_ptr<int> data_;
};

2. Swap функции

template<typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b))) {
    a.swap(b);
}

3. Функции, которые не могут бросить исключения

class Logger {
public:
    void log(const std::string& message) noexcept {
        // Логирование не должно бросать исключения
        // иначе маскирует оригинальное исключение
        try {
            file_ << message << std::endl;
        } catch (...) {
            // Обработаем, но не пробросим
        }
    }
private:
    std::ofstream file_;
};

4. Функции, где нет динамической памяти

int add(int a, int b) noexcept {
    return a + b;
}

bool isEmpty(const std::vector<int>& v) noexcept {
    return v.empty();
}

Как проверить, что функция не бросает исключения?

// Способ 1: Использовать noexcept() оператор
template<typename T>
void safely(T& t) {
    if constexpr (noexcept(t.cleanup())) {
        t.cleanup(); // знаем, что безопасно
    } else {
        try {
            t.cleanup();
        } catch (...) {
            // обработка
        }
    }
}

// Способ 2: type_traits
#include <type_traits>

static_assert(
    std::is_nothrow_move_constructible_v<std::vector<int>>,
    "vector должен иметь nothrow move constructor"
);

Стандартная библиотека

В std::vector, std::list, std::map и других контейнерах используется noexcept(noexcept(T::move_constructor)) для принятия решений о рост ре аллокации:

// Внутри std::vector::push_back(T&&)
if (size_ == capacity_) {
    // Нужна реаллокация
    // Если T::move_constructor noexcept → используем move
    // Если нет → используем copy (более безопасно)
    reallocate();
}

Когда НЕ использовать noexcept

Красные флаги:

// ❌ Неправильно: функция может бросить, но помечена noexcept
int riskyFunction() noexcept {
    std::vector<int> v;
    v.at(100); // ⚠️ бросит std::out_of_range!
}

// ❌ Неправильно: слишком оптимистично
int fileOperation() noexcept {
    std::ifstream file("missing.txt"); // может не открыть
    // ...
}

// ✅ Правильно: честный контракт
int safeAdd(int a, int b) noexcept {
    return a + b; // действительно не может бросить
}

Практические советы

  1. Помечайте move операции — это критично для производительности
  2. Помечайте деструкторы — всегда noexcept
  3. Помечайте операции базовых типов — add, multiply, etc.
  4. Будьте честны — если неуверены, не пишите noexcept
  5. Проверяйте стандартную библиотеку — она наполнена noexcept

Вывод

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