面试题答案
一键面试在C++中,如果在构造函数中抛出异常,会发生以下情况:
- 对象构造不完全:构造函数抛出异常意味着对象没有完全构造成功。此时,该对象的生命周期并未开始,调用构造函数的代码将不会得到一个有效的对象实例。
- 自动释放已分配资源:对于对象中已经构造的成员变量,它们的析构函数会被自动调用。这是因为C++的异常处理机制会自动进行栈展开(stack unwinding),清理已经构造的局部对象。
析构函数在这种情况下的调用:
如上述所说,在构造函数抛出异常时,对于已经构造的成员变量,其析构函数会被调用。例如,考虑以下类:
class Resource {
public:
Resource() { std::cout << "Resource constructed" << std::endl; }
~Resource() { std::cout << "Resource destructed" << std::endl; }
};
class Container {
public:
Resource res;
Container() {
throw std::runtime_error("Constructor exception");
}
};
在Container
的构造函数中抛出异常时,Resource
成员变量res
已经构造,它的析构函数会被调用,输出Resource destructed
。
如果析构函数中也抛出异常,会产生以下影响:
- 程序终止风险:如果在析构函数中抛出异常,并且这个异常没有在析构函数内部被捕获,而此时又有另一个异常正在被处理(例如构造函数抛出异常导致栈展开时),那么
std::terminate()
函数会被调用,导致程序异常终止。这是因为C++不允许在异常处理过程中再抛出新的异常,除非新异常在内部被捕获并处理。
设计代码来妥善处理这些异常情况,确保程序稳定性和资源正确释放:
- 在构造函数中避免资源泄露:使用智能指针管理资源,这样即使构造函数抛出异常,资源也会被正确释放。例如:
#include <memory>
#include <stdexcept>
#include <iostream>
class Resource {
public:
Resource() { std::cout << "Resource constructed" << std::endl; }
~Resource() { std::cout << "Resource destructed" << std::endl; }
};
class Container {
public:
std::unique_ptr<Resource> res;
Container() {
res = std::make_unique<Resource>();
if (/* some condition */) {
throw std::runtime_error("Constructor exception");
}
}
};
int main() {
try {
Container c;
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
这里Container
使用std::unique_ptr<Resource>
管理Resource
对象,若构造函数抛出异常,std::unique_ptr
会自动释放Resource
。
2. 在析构函数中捕获异常:如果析构函数必须执行可能抛出异常的操作,要在析构函数内部捕获异常并处理,避免异常传播出去。例如:
class AnotherResource {
public:
AnotherResource() { std::cout << "AnotherResource constructed" << std::endl; }
~AnotherResource() {
try {
// 可能抛出异常的操作
} catch (...) {
// 捕获异常并处理,比如记录日志
std::cerr << "Exception in destructor caught and handled" << std::endl;
}
}
};
这样在析构函数中捕获异常,就不会导致std::terminate()
被调用,保证了程序的稳定性。