在C++里,异常处理机制本身通过值抛出、引用捕获来保证异常对象的安全传递,但涉及原始指针和引用时,开发者容易遇到资源泄漏或悬空绑定的问题。理解编译器在栈展开过程中的行为,才能正确处理这些场景。

异常对象与引用捕获
C++标准规定,通过throw表达式创建的异常对象会在专用存储区构造,捕获时推荐使用const引用,这样既避免拷贝又能安全访问异常数据。
#include <iostream>
#include <stdexcept>
void func() {
throw std::runtime_error("error occurred");
}
int main() {
try {
func();
} catch (const std::exception& e) {
// 通过引用捕获,避免对象切片且安全
std::cout << e.what() << std::endl;
}
return 0;
}
抛出指针带来的风险
如果抛出一个原始指针,例如throw new std::runtime_error("err"),那么捕获方必须记得delete,否则内存泄漏。更危险的是若指针指向栈对象,抛出后栈已展开,捕获到的就是悬空指针。
#include <stdexcept>
void bad_throw() {
throw new std::runtime_error("leak risk"); // 不推荐
}
void caller() {
try {
bad_throw();
} catch (std::runtime_error* p) {
// 必须手动释放,容易遗忘
delete p;
}
}
引用绑定到局部对象的问题
不要抛出对局部变量的引用。栈展开会销毁局部对象,捕获的引用将悬空。以下示例展示错误写法:
#include <stdexcept>
void wrong() {
std::runtime_error e("local");
throw e; // 实际拷贝到异常存储,原e会被销毁,但抛引用才危险
}
void wrong_ref() {
static std::runtime_error e("static ok");
throw &e; // 若抛局部引用则悬空,这里仅说明概念
}
使用智能指针管理资源
当异常路径上持有动态资源,应使用std::unique_ptr或std::shared_ptr,它们在栈展开时自动释放,避免裸指针问题。
#include <memory>
#include <stdexcept>
void safe() {
auto ptr = std::make_unique<int>(10);
// 若此处抛异常,ptr自动释放
throw std::runtime_error("with resource");
}
总结建议
- 优先按值抛出,按const引用捕获异常。
- 避免抛出原始指针,必要时用智能指针封装。
- 绝不抛出指向局部变量的指针或引用。
- 利用RAII对象管理资源,保证栈展开安全。