面试题答案
一键面试异常抛出、捕获过程中的性能开销
- 抛出开销
- 栈展开:当异常被抛出时,程序会开始栈展开过程。这意味着从抛出点到最近的异常处理程序之间的所有自动变量(局部变量)的析构函数都会被调用。这个过程需要遍历调用栈,确定哪些对象需要析构,这会带来一定的时间开销。
- 对象拷贝:如果异常对象是通过值抛出的,会发生对象拷贝操作,将异常对象从抛出点拷贝到异常处理程序能够访问的地方。这不仅有时间开销,还可能涉及内存分配,如果异常对象较大,开销会更明显。
- 捕获开销
- 匹配查找:异常处理机制需要在调用栈中查找能够处理该异常的处理程序。它从抛出点开始,沿着调用栈向上查找,直到找到一个匹配的catch块。如果调用栈很深,这个查找过程可能会花费较多时间。
结合RAII原则保证资源正确管理
- RAII原则简介:RAII(Resource Acquisition Is Initialization)即资源获取即初始化。它的核心思想是将资源的获取和释放绑定到对象的生命周期上。当对象构造时获取资源,对象析构时释放资源。
- 在异常处理中使用RAII:在函数中使用局部对象来管理资源,当函数由于异常退出时,这些局部对象的析构函数会自动被调用,从而正确释放资源。例如,使用
std::unique_ptr
或std::shared_ptr
来管理动态分配的内存,使用std::lock_guard
或std::unique_lock
来管理互斥锁等。
可能出现的资源管理问题及解决方案
- 问题举例:
void someFunction() {
int* ptr = new int[10];
// 这里可能抛出异常
throw std::runtime_error("Some error");
delete[] ptr; // 这行代码永远不会执行,导致内存泄漏
}
- 解决方案(使用RAII):
#include <memory>
void someFunction() {
std::unique_ptr<int[]> ptr(new int[10]);
// 这里可能抛出异常
throw std::runtime_error("Some error");
// 异常抛出时,std::unique_ptr的析构函数会自动释放内存
}
另一个例子,在多线程环境中管理互斥锁:
#include <mutex>
std::mutex mtx;
void someThreadFunction() {
mtx.lock();
// 这里可能抛出异常
throw std::runtime_error("Thread error");
mtx.unlock(); // 这行代码永远不会执行,导致死锁
}
使用std::lock_guard
解决:
#include <mutex>
std::mutex mtx;
void someThreadFunction() {
std::lock_guard<std::mutex> lock(mtx);
// 这里可能抛出异常
throw std::runtime_error("Thread error");
// 异常抛出时,std::lock_guard的析构函数会自动解锁互斥锁
}