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

Какие знаешь гарантии безопасности исключений?

2.0 Middle🔥 131 комментариев
#Исключения и обработка ошибок

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

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

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

Гарантии безопасности исключений в C++

Exception safety — это свойство кода, которое гарантирует корректное поведение при выбросе исключения. Существует четыре уровня гарантий, каждый из которых имеет разные требования для программиста.

Четыре уровня гарантий

1. No-throw guarantee (nothrow)

Функция никогда не выбросит исключение:

class Resource {
public:
    void reset() noexcept {  // Гарантирует no-throw
        data = nullptr;
    }
    
    int getValue() const noexcept {
        return value;
    }
private:
    int value = 0;
    void* data = nullptr;
};

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

  • Деструкторы (ВСЕГДА noexcept)
  • Move операции (помечай noexcept)
  • Swap операции
class MyClass {
public:
    ~MyClass() noexcept { }  // Деструктор НИКОГДА не выбросит
    
    MyClass(MyClass&& other) noexcept { }  // Move НЕ выбросит
    
    MyClass& operator=(MyClass&& other) noexcept { }
};

2. Strong guarantee (rollback)

Если функция выбросит исключение, программа остаётся в исходном состоянии (как будто функция не вызывалась):

class Account {
private:
    double balance = 0;
    std::string history;
public:
    // Strong guarantee
    void deposit(double amount) {
        if (amount <= 0) {
            throw std::invalid_argument("Amount must be positive");
        }
        
        // Выполняем в правильном порядке
        double newBalance = balance + amount;
        std::string newEntry = "deposit: " + std::to_string(amount);
        
        // Если здесь выбросит - ничего не изменилось
        history += newEntry;  // Может выбросить
        balance = newBalance;  // После успешного добавления в history
    }
};

Copy-and-swap для Strong guarantee:

class Vector {
private:
    std::unique_ptr<int[]> data;
    size_t size = 0;
public:
    // Strong guarantee через copy-and-swap
    Vector& operator=(const Vector& other) {
        Vector temp(other);  // Копируем (может выбросить)
        swap(*this, temp);   // Swap никогда не выбросит
        return *this;
        // temp удалится с исходными данными
    }
    
    friend void swap(Vector& a, Vector& b) noexcept {
        std::swap(a.data, b.data);
        std::swap(a.size, b.size);
    }
};

3. Basic guarantee (catch and continue)

Если функция выбросит исключение, объект остаётся в валидном, но неопределённом состоянии:

class DataProcessor {
public:
    // Basic guarantee
    void processFile(const std::string& filename) {
        std::ifstream file(filename);
        if (!file) {
            throw std::runtime_error("Cannot open file");
        }
        
        std::string line;
        while (std::getline(file, line)) {
            processLine(line);  // Может выбросить
            // Если выбросит - частичные данные обработаны
            // но объект ещё пригоден для использования
        }
    }
    
    // Состояние валидно, но какие данные обработаны - неизвестно
};

4. No guarantee

Если выбросит исключение — программа может быть в любом состоянии:

// ПЛОХО - no guarantee
void unsafeFunction() {
    int* ptr = new int[1000];
    processData(ptr);  // Может выбросить
    delete[] ptr;      // Если выбросит - утечка памяти
}

// ХОРОШО - используй RAII
void safeFunction() {
    auto ptr = std::make_unique<int[]>(1000);
    processData(ptr.get());  // Может выбросить
    // ptr автоматически удалится в любом случае
}

RAII для Exception Safety

Resource Acquisition Is Initialization:

class FileGuard {
private:
    std::ofstream file;
public:
    FileGuard(const std::string& filename) {
        file.open(filename);
        if (!file) throw std::runtime_error("Cannot open");
    }
    
    ~FileGuard() {  // noexcept
        file.close();
    }
    
    void write(const std::string& data) {
        file << data;
    }
};

int main() {
    try {
        FileGuard guard("data.txt");
        guard.write("Hello");
        processData();  // Может выбросить
        // guard автоматически закроется при выходе
    } catch (...) {
        // Файл уже закрыт благодаря RAII
    }
}

Exception-safe операции с контейнерами

std::vector<int> v = {1, 2, 3};

// ПЛОХО - iterator invalidation при exception
try {
    for (auto it = v.begin(); it != v.end(); ++it) {
        process(*it);
        v.push_back(nextValue());  // Может выбросить и инвалидировать it
    }
} catch (...) { }

// ХОРОШО - создай копию
try {
    auto copy = v;
    for (int x : copy) {
        process(x);
        v.push_back(nextValue());
    }
} catch (...) {
    // v может быть в промежуточном состоянии, но валидно
}

Спецификации исключений (C++11)

// noexcept - никогда не выбросит
void safe() noexcept { }

// noexcept(expression) - условная
template<typename T>
void genericFunction() noexcept(std::is_nothrow_move_constructible_v<T>) { }

// Проверка в runtime
if (std::is_nothrow_move_constructible_v<MyClass>) {
    // Используй move, это безопасно
} else {
    // Используй copy
}

Инструменты для проверки

// Проверь guarantee функции
void analyzer() {
    auto strongGuarantee = []() noexcept {
        try {
            // Strong guarantee code
        } catch (...) {
            // rollback всего
            throw;
        }
    };
}

// Компилятор предупредит если:
// - noexcept функция выбросит
// - Деструктор может выбросить
// - Move не noexcept но используется

Практический чек-лист

ВСЕГДА:

  • Помечай деструкторы noexcept
  • Помечай move операции noexcept
  • Используй RAII для ресурсов
  • Тестируй exception paths

НИКОГДА:

  • Не выбрасывай из деструкторов
  • Не используй try-catch как control flow
  • Не забывай про stack unwinding
  • Не изменяй состояние в деструкторе

Примеры документирования

/// @post Either succeeds completely or makes no changes
/// @guarantee Strong exception safety
void criticalOperation() { }

/// @post May be partially completed, but remains valid
/// @guarantee Basic exception safety
void bulkLoad(const std::vector<Data>& items) { }

Понимание гарантий исключений — это основа надёжного многопоточного C++ кода.