C++异常机制:抛出、捕获与栈展开

 更新时间:2026年05月21日 10:58:43   作者:无限进步_  
这篇文章把异常的抛出、捕获、栈展开过程以及类型匹配规则梳理一遍,最后用一组自定义异常体系展示实际项目中怎么用,结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

C++异常机制通过 throw(抛出)、try/catch(捕获)和栈展开(Stack Unwinding)实现结构化错误处理,核心是“检测与分离”+“自动资源清理”。‌‌‌

  • ‌抛出(throw)‌:在检测到错误时用 throw 表达式; 抛出一个异常对象(可为内置类型、字符串或类对象,推荐继承 std::exception);抛出后立即跳转,后续代码不执行,并生成异常对象的拷贝用于传播。
  • ‌捕获(try/catch)‌:try 块包裹可能异常的代码,后续接一个或多个 catch(类型 参数) 处理匹配异常;‌类型需精确匹配或允许隐式转换‌(如 const 权限缩小、派生类→基类引用/值);catch(...) 为万能兜底,但无法获取异常详情;‌匹配的是离抛出点最近的、类型兼容的 catch‌。
  • ‌栈展开(Stack Unwinding)‌:若当前函数无匹配 catch,‌自动逆序析构当前函数所有已构造的局部对象‌,逐层返回调用栈,直至找到匹配 catch 或抵达 main 未捕获则调用 std::terminate() 终止程序;此过程确保 RAII 资源(如锁、内存)正确释放,‌但析构函数自身不得抛异常‌(否则立即 terminate)。‌‌

异常传播中,‌throw;(无表达式)可在 catch 内重新抛出原异常‌(保持原始类型);C++11 起弃用 throw() 规格说明,改用 noexcept 声明“不抛异常”,违反则直接 terminate。合理使用可分离错误逻辑与主流程,但滥用影响性能与可读性。‌‌

错误处理是程序里绕不开的话题。C语言用错误码,函数返回一个int,调用方去查表,麻烦不说,还容易把业务逻辑和错误处理搅在一起。C++的异常机制提供了一种不同的思路:把发现错误处理错误的代码分开。出错的模块只负责抛出异常,至于怎么处理,由调用链上合适的捕获点决定,两边互不侵入。

这篇文章把异常的抛出、捕获、栈展开过程以及类型匹配规则梳理一遍,最后用一组自定义异常体系展示实际项目中怎么用。

1. 抛出与捕获的基本姿势

1.1 throw干了什么

当程序运行到throw语句时,会构造一个异常对象,然后函数的剩余代码不再执行,控制权开始沿着调用链向上转移,寻找匹配的catch。这个过程有三个关键点:

  • throw后面的语句被跳过。
  • 当前函数可能提前退出。
  • 局部对象会按顺序析构(栈展开时完成)。

throw的异常对象如果是局部的,会生成一份拷贝交给catch,原对象在栈展开过程中销毁。这份拷贝会在catch处理完毕后销毁。

1.2 try/catch怎么接

void Func() {
    int len, time;
    cin >> len >> time;
    try {
        cout << Divide(len, time) << endl;
    } catch (const char* errmsg) {
        cout << errmsg << endl;
    }
    cout << "Func continuing..." << endl;
}
double Divide(int a, int b) {
    if (b == 0) {
        string s("Divide by zero condition!");
        throw s;
    }
    return (double)a / (double)b;
}

Divide里抛出的是string对象,而Func里捕获的类型是const char*,不匹配。所以Func的catch会被跳过,继续向外层调用者(这里是main)寻找匹配的catch(string)。如果main也找不到,程序就调用terminate终止。

catch的匹配规则

  • 一般情况下要求类型完全匹配。
  • 允许从非常量向常量的转换(权限缩小方向),但不允许其他隐式类型转换(比如int转double)。
  • 数组会被转换为指向元素类型的指针,函数转换为函数指针。
  • 支持派生类向基类的转换。这个在实际项目中非常实用,后面会展开。

如果外层没有匹配的catch,main函数最后通常会写一个catch(...)兜底,防止程序崩溃,但它只能捕获,没法知道具体错误信息。

2. 栈展开:异常的传播路径

栈展开(stack unwinding)是理解异常行为的关键。从throw开始,编译器会依次查找:

  • 检查throw本身是否在try块内。如果在,看当前try块的catch有没有匹配的。
  • 有匹配就跳到catch执行,之后从这个catch结束后的第一条语句继续运行。
  • 没有匹配,则退出当前函数,析构所有局部对象,回到调用方函数的调用点,重复第一步。
  • 到达main还没匹配,调用terminate终止程序。

假设调用链是:main() → func3() → func2() → func1()func1抛出异常,catch在main里。它会依次退出func1func2func3的栈帧,直到在main里找到匹配的catch。这里“退出”不是简单的跳转,而是沿着调用链,层层析构局部对象,这保证了RAII资源能被正确释放。

3. 自定义异常体系:利用派生类到基类的转换

在实际项目中,通常不会到处抛string或基本类型,而是构建一个异常类体系,基类可以是std::exception或者自己写的类,各模块派生自己的异常类型。捕获时只捕获基类引用,就能统一处理所有异常。

// 基类
class Exception {
public:
    Exception(const string& errmsg, int id)
        : _errmsg(errmsg), _id(id) {}
    virtual string what() const { return _errmsg; }
    int getid() const { return _id; }
protected:
    string _errmsg;
    int _id;
};
// 各模块派生自己的异常
class SqlException : public Exception {
public:
    SqlException(const string& errmsg, int id, const string& sql)
        : Exception(errmsg, id), _sql(sql) {}
    string what() const override {
        return "SqlException:" + _errmsg + "->" + _sql;
    }
private:
    string _sql;
};
class CacheException : public Exception {
public:
    CacheException(const string& errmsg, int id)
        : Exception(errmsg, id) {}
    string what() const override {
        return "CacheException:" + _errmsg;
    }
};
class HttpException : public Exception {
public:
    HttpException(const string& errmsg, int id, const string& type)
        : Exception(errmsg, id), _type(type) {}
    string what() const override {
        return "HttpException:" + _type + ":" + _errmsg;
    }
private:
    string _type;
};

业务函数模拟抛出各类异常:

void SQLMgr() {
    if (rand() % 7 == 0)
        throw SqlException("权限不足", 100, "select * from name = '张三'");
    cout << "SQLMgr 调用成功" << endl;
}
void CacheMgr() {
    if (rand() % 5 == 0)
        throw CacheException("权限不足", 100);
    else if (rand() % 6 == 0)
        throw CacheException("数据不存在", 101);
    cout << "CacheMgr 调用成功" << endl;
    SQLMgr();
}
void HttpServer() {
    if (rand() % 3 == 0)
        throw HttpException("请求资源不存在", 100, "get");
    else if (rand() % 4 == 0)
        throw HttpException("权限不足", 101, "post");
    cout << "HttpServer调用成功" << endl;
    CacheMgr();
}

主函数里只需要捕获基类引用:

int main() {
    srand(time(0));
    while (1) {
        this_thread::sleep_for(chrono::seconds(1));
        try {
            HttpServer();
        } catch (const Exception& e) {
            cout << e.what() << endl;
        } catch (...) {
            cout << "Unknown Exception" << endl;
        }
    }
}

捕获基类引用配合虚函数what(),既能拿到具体类型的信息,又不需要写一堆分支类型的catch。新增一个派生类异常,只要继承Exception并重写what(),上层代码一行不改。这就是开放-封闭原则在错误处理中的体现。

这里有一个细节:catch的参数应该用引用,否则会发生拷贝切片,派生类的额外信息就丢了。

小结

异常机制的本质是把错误检测和处理解耦,代价是引入了栈展开带来的控制流复杂性和资源管理风险。下一篇文章会顺着这个思路往下走:异常重新抛出、异常安全问题、以及C++11引入的noexcept规范——它们试图解决“异常本身带来的问题”。

到此这篇关于C++异常机制:抛出、捕获与栈展开的文章就介绍到这了,更多相关C++异常抛出、捕获与栈展开内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++关于/2和>>1的区别说明

    C++关于/2和>>1的区别说明

    这篇文章主要介绍了C++关于/2和>>1的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Qt QWidget实现图片旋转动画

    Qt QWidget实现图片旋转动画

    这篇文章主要为大家详细介绍了如何使用了Qt和QWidget实现图片旋转动画效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-12-12
  • Qt实现俄罗斯方块

    Qt实现俄罗斯方块

    这篇文章主要为大家详细介绍了Qt实现俄罗斯方块,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • C++基于reactor的服务器百万并发实现与讲解

    C++基于reactor的服务器百万并发实现与讲解

    这篇文章主要介绍了C++基于reactor的服务器百万并发实现与讲解,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • C++实现中缀表达式转化为后缀表达式详解

    C++实现中缀表达式转化为后缀表达式详解

    这篇文章主要为大家详细介绍了如何利用C++解决实现中缀表达式转换为后缀表达式的问题,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C语言报错:Buffer Overflow的原因和解决办法

    C语言报错:Buffer Overflow的原因和解决办法

    Buffer Overflow是C语言中常见且危险的内存错误之一,它通常在程序试图向缓冲区(如数组或内存块)写入超过其容量的数据时发生,本文将详细介绍Buffer Overflow的产生原因,提供多种解决方案,需要的朋友可以参考下
    2024-07-07
  • c++如何实现Base64算法

    c++如何实现Base64算法

    这篇文章主要介绍了c++如何实现Base64算法,文中讲解非常细致,帮助大家更好的理解和学习c++,感兴趣的朋友可以了解下
    2020-08-08
  • C语言实现数字雨效果

    C语言实现数字雨效果

    这篇文章主要为大家详细介绍了C语言实现数字雨效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • C语言之如何求三次方根

    C语言之如何求三次方根

    这篇文章主要介绍了C语言之如何求三次方根问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • c++如何保存vector到文件

    c++如何保存vector到文件

    这篇文章主要介绍了c++如何保存vector到文件,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11

最新评论