C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++异常抛出、捕获与栈展开

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

作者:无限进步_

这篇文章把异常的抛出、捕获、栈展开过程以及类型匹配规则梳理一遍,最后用一组自定义异常体系展示实际项目中怎么用,结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

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

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

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

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

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

1.1 throw干了什么

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

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的匹配规则

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

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

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

假设调用链是: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++异常抛出、捕获与栈展开内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文