try-catch基本写法是用try包裹可能抛异常代码,catch按顺序精确匹配异常类型(支持派生类→基类隐式转换),推荐使用const引用避免切片和拷贝;无匹配时栈展开,最终调用std::terminate终止程序。

try-catch 基本写法和执行流程
直接用 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 表达式与自定义异常类
throw 后面可以是任意类型表达式,但强烈建议只抛出继承自 std::exception 的类对象(或标准库已提供的异常如 std::logic_error)。自己定义异常类时,至少实现 what() 成员函数并返回 C 风格字符串。
容易踩的坑:在 what() 中返回局部变量的 c_str(),导致悬垂指针;或者抛出临时对象后在 catch 中取地址,引发未定义行为。
立即学习“C++免费学习笔记(深入)”;
- 自定义异常类的
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");
异常安全与资源管理(RAII 是关键)
C++ 异常处理本身不提供资源释放机制。一旦抛出异常,栈展开过程中,所有已构造的局部对象会按逆序析构——这是 RAII(Resource Acquisition Is Initialization)生效的前提。但如果手动用 new 分配内存、用 fopen 打开文件,又没在 catch 里显式清理,就会泄漏。
动态WEB网站中的PHP和MySQL详细反映实际程序的需求,仔细地探讨外部数据的验证(例如信用卡卡号的格式)、用户登录以及如何使用模板建立网页的标准外观。动态WEB网站中的PHP和MySQL的内容不仅仅是这些。书中还提到如何串联JavaScript与PHP让用户操作时更快、更方便。还有正确处理用户输入错误的方法,让网站看起来更专业。另外还引入大量来自PEAR外挂函数库的强大功能,对常用的、强大的包
典型错误场景:在 try 块里 new 一块内存,然后抛异常,catch 块里忘了 delete;或者用了原始指针管理资源,却期望异常发生时自动释放。
- 优先使用智能指针(
std::unique_ptr、std::shared_ptr)、容器(std::vector)、文件流(std::ifstream)等自带析构逻辑的类型 - 避免在构造函数中抛出异常的同时持有裸资源(比如 new 成功但后续初始化失败)
- 不要在析构函数里抛异常(C++11 起默认为
noexcept,否则直接调用std::terminate())
什么时候不该用 try-catch?
异常适合处理“罕见、不可预测、无法在当前上下文恢复”的错误,比如磁盘满、网络断连、无效输入导致的解析失败。但像循环索引越界、空指针解引用这类逻辑错误,应该靠断言(assert)或防御性编程提前拦截,而不是依赖异常捕获。
性能上,现代编译器对无异常抛出的代码几乎零开销(zero-cost exceptions),但频繁抛出/捕获仍比返回错误码慢得多。尤其在实时或嵌入式场景,很多项目直接禁用异常(-fno-exceptions)。
- 不应用异常替代常规控制流(比如用
throw实现 goto 效果) - 系统级 API(如 POSIX 函数)通常用返回码和
errno,不要包装成异常再抛,除非有明确封装层职责 - 跨动态库边界抛异常风险高(ABI 不统一),尽量在库接口层吞掉异常,转为错误码或日志
异常真正难的不是语法,而是判断哪一层该捕获、哪一层该继续传播,以及确保每条路径都维持对象不变量——这需要结合具体模块职责来设计,不能只看 try 和 catch 写在哪。









