信息发布→ 登录 注册 退出

C++如何处理异常(try-catch)?(程序的错误捕获与处理)

发布时间:2026-01-09

点击量:
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 中取地址,引发未定义行为。

  • 自定义异常类的 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 里显式清理,就会泄漏。

典型错误场景:在 try 块里 new 一块内存,然后抛异常,catch 块里忘了 delete;或者用了原始指针管理资源,却期望异常发生时自动释放。

  • 优先使用智能指针(std::unique_ptrstd::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 不统一),尽量在库接口层吞掉异常,转为错误码或日志

异常真正难的不是语法,而是判断哪一层该捕获、哪一层该继续传播,以及确保每条路径都维持对象不变量——这需要结合具体模块职责来设计,不能只看 trycatch 写在哪。

标签:# 抛出  # 风格字符串  # 指针  # 继承  # 接口  # ifstream  # 空指针  # 切片  # delete  # 对象  # 循环  # 自定义  # 这是  # 就会  # 还没  # 放在  # 派生类  # 隐式  # 多个  # 什么时候  # fopen  # ai  # c++  # 隐式转换  # String  # Resource  # 封装  # 成员函数  # 构造函数  # 析构函数  #   # try  # throw  # catch  # const  # goto  # 局部变量  # 字符串  # errno  # int  
在线客服
服务热线

服务热线

4008888355

微信咨询
二维码
返回顶部
×二维码

截屏,微信识别二维码

打开微信

微信号已复制,请打开微信添加咨询详情!