try-catch基本写法是用try包裹可能抛异常代码,catch按顺序精确匹配异常类型(支持派生类→基类隐式转换),推荐使用const引用避免切片和拷贝;无匹配时栈展开,最终调用std::terminate终止程序。
直接用 try 包住可能抛出异常的代码,用 catch 捕获对应类型的异常。C++ 不会自动向上查找匹配的 catch 块,类型必须精确匹配(或能隐式转换,比如派生类 → 基类),否则异常会继续向外传播,最终调用 std::terminate() 终止程序。
常见错误现象:捕获了 int 却抛出 std::string,或者写了 catch (std::exception e)(传值)却没加 &,导致对象被切片或额外拷贝。
catch 参数推荐用 const std::exception& 或更具体的异常类型引用,避免拷贝和切片catch 块按顺序匹配,更具体的类型要放在更通用的类型前面(比如先 catch (const std::out_of_range&),再 catch (const std::exception&))catch 时,栈会持续展开,若到 main() 还没被捕获,程序直接终止try {
throw std::runtime_error("something went wrong");
} catch (const std::runtime_error& e) {
std::cout << "Caught: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "Fallback: " << e.what() << "\n";
}throw 后面可以是任意类型表达式,但强烈建议只抛出继承自 std::exception 的类对象(或标准库已提供的异常如 std::logic_error)。自己定义异常类时,至少实现 what() 成员函数并返回 C 风格字符串。
容易踩的坑:在 what() 中返回局部变量的 c_str(),导致悬垂指针;或者抛出临时对象后在 catch 中取地址,引发未定义行为。
what() 返回值应指向生命周期足够长的存储(比如 std::string 成员的 c_str(),且该 std::string 是类的成员)noexcept,却意外抛出异常,会立即调用 std::terminate()
class MyException : public std::exception {
std::string msg_;
public:
MyException(const std::string& msg) : msg_(msg) {}
const char* what() const noexcept override {
return msg_.c_str();
}
};
// 使用
throw MyException("invalid input");
C++ 异常处理本身不提供资源释放机制。一旦抛出异常,栈展开过程中,所有已构造的局部对象会按逆序析构——这是 RAII(Resource Acquisition Is Initialization)生效的前提。但如果手动用 new 分配内存、用 fopen 打开文件,又没在 catch 里显式清理,就会泄漏。
典型错误场景:在 try 块里 new 一块内存,然后抛异常,catch 块里忘了 delete;或者用了原始指针管理资源,却期望异常发生时自动释放。
std::uni
que_ptr、std::shared_ptr)、容器(std::vector)、文件流(std::ifstream)等自带析构逻辑的类型noexcept,否则直接调用 std::terminate())异常适合处理“罕见、不可预测、无法在当前上下文恢复”的错误,比如磁盘满、网络断连、无效输入导致的解析失败。但像循环索引越界、空指针解引用这类逻辑错误,应该靠断言(assert)或防御性编程提前拦截,而不是依赖异常捕获。
性能上,现代编译器对无异常抛出的代码几乎零开销(zero-cost exceptions),但频繁抛出/捕获仍比返回错误码慢得多。尤其在实时或嵌入式场景,很多项目直接禁用异常(-fno-exceptions)。
throw 实现 goto 效果)errno,不要包装成异常再抛,除非有明确封装层职责异常真正难的不是语法,而是判断哪一层该捕获、哪一层该继续传播,以及确保每条路径都维持对象不变量——这需要结合具体模块职责来设计,不能只看 try 和 catch 写在哪。