Middle+
What is the peculiarity of throwing exceptions in constructors and destructors?
sobes.tech AI
Answer from AI
Exception throwing in constructor:
- If an exception is thrown in a constructor, the destructor of the object is not called.
- It is necessary to take care of releasing already allocated resources before the exception is thrown or use the RAII (Resource Acquisition Is Initialization) idiom for resource management.
- A partially constructed object is in an undefined state.
Exception throwing in destructor:
- Throwing an exception from a destructor during stack unwinding due to another exception leads to a call to
std::terminate, which terminates the program. This is very bad. - Destructors should be
noexcept. - If an operation that may throw an exception (e.g., closing a file) needs to be performed in a destructor, it should be handled inside the destructor (e.g., logging) instead of propagating the exception further.
#include <iostream>
#include <fstream>
class ResourceHolder {
std::ofstream file;
bool opened = false;
public:
ResourceHolder(const std::string& filename) {
file.open(filename);
if (!file.is_open()) {
// Throw exception in constructor
throw std::runtime_error("Failed to open file: " + filename);
}
opened = true;
std::cout << "Resource acquired successfully." << std::endl;
}
~ResourceHolder() noexcept { // Destructor marked noexcept
if (opened) {
try {
file.close();
std::cout << "Resource released successfully." << std::endl;
} catch (const std::exception& e) {
// Handle exception inside destructor
std::cerr << "Exception caught in destructor: " << e.what() << std::endl;
// Do not throw further
}
} else {
std::cout << "Resource release skipped (not opened)." << std::endl;
}
}
};
int main() {
try {
// Example of exception in constructor
// ResourceHolder res("non_existent_file.txt");
// Example of successful construction and release
ResourceHolder res("test.txt");
} catch (const std::runtime_error& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
// Example of incorrect destructor (for illustration, DO NOT REPEAT)
/*
class BadDestructor {
int* data;
public:
BadDestructor() : data(new int(10)) {}
~BadDestructor() {
delete data;
// Simulate exception in destructor (DO NOT DO THIS)
// throw std::runtime_error("Exception from bad destructor");
}
};
try {
BadDestructor bad_obj;
throw std::runtime_error("Another exception"); // Throw another exception
} catch (...) {
// When an exception occurs in destructor during stack unwinding
// std::terminate will be called
std::cerr << "Caught some exception in main." << std::endl;
}
*/
return 0;
}
| Feature | Constructor | Destructor |
|---|---|---|
| Destructor call | Not called if an exception is thrown | Usually called (if construction is successful) |
| Undefined state | Partially constructed object | - |
| RAII | Important for resource management | - |
noexcept |
Not mandatory but useful to know about error possibility | Highly recommended to be noexcept |
| Stack unwinding | Participates in the process | Throwing an exception during unwinding leads to std::terminate |