C++ 异常处理机制

1. 异常的概念

异常处理机制是C++中一种强大的错误处理方式。它允许程序中独立开发的部分,在运行时针对出现的问题进行通信并做出相应的处理。异常将问题的检测与问题的解决过程分离开来:程序的一部分负责检测错误,然后将解决问题的任务传递给程序的另一部分。检测环节无需知道问题处理模块的所有细节。

与C语言主要通过错误码(对错误信息进行分类编号)来处理错误不同,C++的异常机制是抛出一个对象。这个对象可以包含比简单的错误码更全面、更丰富的信息,使得错误处理更加灵活和强大。

2. 异常的抛出与捕获

在C++中,当程序出现问题时,我们通过 throw 一个对象来引发一个异常。被选中的异常处理代码(catch 块)是由调用链中与抛出对象类型匹配,且距离抛出位置最近的那一个决定的。当 throw 语句执行时,其后的代码将不再被执行。程序的执行流会直接从 throw 的位置,跳转到与之匹配的catch模块。控制权的转移可能导致调用链中的函数提前退出。同时,在沿着调用链查找匹配的 catch 块的过程中,沿途创建的所有局部对象都会被销毁(这一过程称为栈展开 )。由于抛出的异常对象可能是一个局部对象,因此系统会生成该对象的一个拷贝,这个拷贝对象将在对应的catch 子句执行完毕后被销毁。这个过程类似于函数的传值返回。
示例代码:基本的异常抛出与捕获

下面是一个简单的示例,演示了异常如何在多层函数调用中被抛出和捕获。

cpp 复制代码
double Divide(int a, int b)
{
    // 当 b == 0 时抛出异常
    if (b == 0)
    {
        string s("Divide by zero condition!");
        throw s; // 抛出 string 类型的异常对象
    }
    else
    {
        return ((double)a / (double)b);
    }
}

void Func()
{
    int len, time;
    cin >> len >> time;
    try
    {
        cout << Divide(len, time) << endl;
    }
    catch (const char* errmsg) // 尝试捕获 const char* 类型的异常
    {
        cout << errmsg << endl;
    }
    cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
}

int main()
{
    while (1)
    {
        try
        {
            Func(); // 调用可能抛出异常的函数
        }
        catch (const string& errmsg) // 捕获 string 类型的异常
        {
            cout << errmsg << endl;
        }
        catch (...) // 捕获任意类型的异常,通常放在最后作为"兜底"
        {
            cout << "未知异常" << endl;
        }
    }
    return 0;
}

分析:
Divide 函数中,如果除数为0,会抛出一个 string 类型的异常。
Func 函数中,它只尝试捕获 const char* 类型的异常,因此无法处理 Divide 抛出的 string 异常。由于在 Func 内部没有找到匹配的 catch,异常会沿着调用链继续向上传递到 main 函数。最终,main 函数中的 catch (const string& errmsg) 成功捕获并处理了这个异常

3. 栈展开

抛出异常后,程序会暂停当前函数的执行,并开始寻找与之匹配的 catch 子句,这个过程就是栈展开。
当前函数查找 :首先检查 throw 语句本身是否位于 try 块内部。如果是,则在该 try 块后面查找匹配的 catch 子句。
向上传递 :如果当前函数中没有找到匹配的 catch,或者有 try/catch 但类型不匹配,则当前函数会终止,其局部对象会被销毁,然后控制权返回给调用它的上层函数。
重复查找 :在上层函数中,重复步骤1的查找过程。如果仍然找不到,就继续向上层传递。
终止程序 :如果最终到达 main 函数,依然没有找到匹配的 catch 子句,程序会调用标准库的 terminate 函数终止运行。
异常处理 :如果在某一层找到了匹配的 catch 子句,程序会跳转到该 catch 块中执行处理代码。

4. 查找匹配的处理代码

在查找匹配的 catch 子句时,默认要求抛出对象的类型与 catch 声明的类型严格匹配。但存在几种例外情况,这些例外使得异常处理更加灵活:
从非constconst的转换 :允许将抛出的非const对象,匹配到接收const引用的 catch 子句(权限缩小)。
数组/函数到指针的转换 :允许将数组类型转换为指向数组元素类型的指针,或将函数类型转换为指向函数的指针。
派生类向基类的转换 :允许将派生类对象匹配到接收基类类型的 catch 子句。这是面向对象编程中非常实用的一种方式,通常用于设计异常类体系。
最佳实践 :在 main 函数的最后,通常建议添加一个 catch(...) 子句。它可以捕获任意类型的异常,防止因未捕获的异常而导致程序意外终止。虽然它无法获取具体的错误信息,但能保证程序最基本的健壮性。

5. 异常重新抛出

有时,我们在 catch 到一个异常对象后,可能需要对错误进行分类处理。对于某些特定类型的错误,我们可能希望在当前函数中进行特殊处理;而对于其他类型的错误,则希望将其重新抛出,交给外层的调用链去处理。

重新抛出一个捕获到的异常非常简单,只需要在 catch 块中直接使用 throw; 语句即可。它会将当前捕获的异常对象原封不动地继续向上抛出。

cpp 复制代码
try {
    // 一些可能抛出多种异常的代码
}
catch (int errCode) {
    if (errCode == 1) {
        // 对错误码为1的情况进行特殊处理
        cout << "处理特定错误" << endl;
    } else {
        // 其他错误码,重新抛出,交给上层处理
        throw;
    }
}
catch (...) {
    // 对未知异常进行记录或转换后,也可以选择重新抛出
    cout << "记录未知异常" << endl;
    throw; // 重新抛出
}

总结:C++的异常处理机制通过 trythrowcatch 三个关键字,提供了一种结构化的、类型安全的错误处理方式。它能够将错误检测与错误处理分离,并通过栈展开和异常重新抛出等特性,为构建健壮的程序提供了坚实的基础。

相关推荐
大阿明2 小时前
Spring BOOT 启动参数
java·spring boot·后端
zyhomepage2 小时前
科技的成就(七十二)
开发语言·人工智能·科技·算法·内容运营
计算机安禾2 小时前
【数据结构与算法】第2篇:C语言核心机制回顾(一):指针、数组与结构体
c语言·开发语言·数据结构·c++·算法·链表·visual studio
dapeng28702 小时前
C++代码重构实战
开发语言·c++·算法
程序员小郭832 小时前
Spring Ai 05 ChatClient Advisor 实战(日志、提示词增强、内容安全)
java·开发语言·前端
hutengyi2 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
tryCbest2 小时前
Python之FastAPI 开发框架(第三篇):高级特性与实战
开发语言·python·fastapi
BestOrNothing_20152 小时前
Ubuntu 22.04 下使用 VS Code 搭建 ROS 2 Humble 集成开发环境
c++·vscode·python·ros2·ubuntu22.04
u0133945272 小时前
How to Run sample.war in a Tomcat Docker Container
java·docker·tomcat