Как избежать Double Exception?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как избежать Double Exception
Double Exception — это критическая ошибка в C++, которая возникает, когда исключение выбрасывается во время обработки другого исключения. Это приводит к немедленному завершению программы (вызов std::terminate()).
Что такое Double Exception?
Double Exception происходит, когда:
- Основное исключение находится в процессе обработки
- Во время обработки (в блоке
catchили деструкторе) возникает новое исключение - C++ Runtime не может обработать ситуацию и вызывает
std::terminate()
#include <iostream>
using namespace std;
class BadDestructor {
public:
~BadDestructor() {
throw runtime_error("Error in destructor!"); // DANGER!
}
};
int main() {
try {
BadDestructor obj;
throw logic_error("First error");
} catch (const exception& e) {
cout << "Caught: " << e.what() << endl;
// Во время раскручивания стека вызывается ~BadDestructor()
// Он выбрасывает исключение -> DOUBLE EXCEPTION -> terminate()
}
return 0;
}
Главное правило: НИКОГДА не выбрасывайте в деструкторах
Деструкторы вызываются автоматически, в том числе во время раскручивания стека при исключении. Если деструктор выбросит исключение, возникнет double exception.
// ❌ ПЛОХО: исключение в деструкторе
class Resource {
public:
~Resource() {
if (!cleanup()) {
throw runtime_error("Cleanup failed!");
}
}
private:
bool cleanup() { /* может вернуть false */ }
};
// ✅ ХОРОШО: никогда не выбрасываем
class Resource {
public:
~Resource() noexcept {
if (!cleanup()) {
// Логируем ошибку, но не выбрасываем
cerr << "Warning: cleanup failed" << endl;
}
}
private:
bool cleanup() { /* может вернуть false */ }
};
Почему noexcept в деструкторах?
С C++11 деструкторы по умолчанию помечены как noexcept. Если деструктор попытается выбросить исключение, это немедленно вызовет std::terminate().
class FileWrapper {
public:
// Деструктор неявно noexcept
~FileWrapper() {
// НИКОГДА не выбрасываем исключения здесь!
file.close(); // должен быть noexcept
}
private:
std::ofstream file;
};
Стратегия обработки ошибок в деструкторах
1. Используй try-catch внутри деструктора
class DatabaseConnection {
public:
~DatabaseConnection() noexcept {
try {
disconnect();
} catch (const exception& e) {
// Логируем, но не выбрасываем
cerr << "Error closing connection: " << e.what() << endl;
}
}
private:
void disconnect() { /* может выбросить */ }
};
2. Используй функцию-помощник с возвращаемым кодом ошибки
class Network {
public:
~Network() noexcept {
int result = closeSocket();
if (result != 0) {
// Логируем ошибку
log("Socket close failed: " + to_string(result));
}
}
private:
int closeSocket() noexcept {
// Возвращаем код ошибки вместо исключения
try { /* ... */ return 0; }
catch (...) { return -1; }
}
};
Обработка исключений в блоках catch
Избегай выбрасывания новых исключений в catch-блоке без необходимости:
// ❌ ПЛОХО: теряем оригинальное исключение
try {
riskyOperation();
} catch (const exception& e) {
throw runtime_error("Operation failed"); // теряем информацию о e
}
// ✅ ХОРОШО: переформатируем с context
try {
riskyOperation();
} catch (const exception& e) {
throw runtime_error(string("Operation failed: ") + e.what());
}
// ✅ ХОРОШО: используем nested exceptions (C++11)
try {
riskyOperation();
} catch (const exception& e) {
try {
throw runtime_error("Operation failed");
} catch (...) {
throw_with_nested(e);
}
}
Практический пример защиты от Double Exception
#include <iostream>
#include <memory>
using namespace std;
class SafeResource {
public:
SafeResource() { cout << "Resource acquired" << endl; }
~SafeResource() noexcept {
// Гарантировано, что не выбросим исключение
cleanup();
}
void cleanup() noexcept {
// Логируем ошибки, но не выбрасываем
try {
// Опасная операция
if (false) { throw runtime_error("Cleanup error"); }
cout << "Resource released" << endl;
} catch (const exception& e) {
cerr << "Cleanup warning: " << e.what() << endl;
}
}
};
int main() {
try {
SafeResource res;
throw logic_error("Main error");
} catch (const exception& e) {
cout << "Caught: " << e.what() << endl;
// При выходе вызовется ~SafeResource(), но она не выбросит исключение
}
cout << "Program continued safely" << endl;
return 0;
}
Чеклист безопасности
- Всегда помечай деструкторы как
noexcept(явно или неявно) - Никогда не выбрасывай исключения в деструкторах
- Оборни опасный код в деструкторе в try-catch
- Используй RAII принцип для гарантии вызова деструкторов
- Логируй ошибки вместо выброса исключений
- Тестируй обработку исключений с использованием специальных флагов
Вывод: Double Exception — это серьёзная проблема, которая приводит к аварийному завершению программы. Главное правило: деструкторы должны быть исключение-безопасными. Используй RAII, try-catch в деструкторах и логирование вместо выброса исключений.