Какие знаешь гарантии безопасности исключений?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Гарантии безопасности исключений в 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++ кода.