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

相关推荐
无限进步_4 分钟前
【C++】只出现一次的数字 II:位运算的三种解法深度解析
数据结构·c++·ide·windows·git·算法·leetcode
网域小星球7 分钟前
C 语言从 0 入门(十七)|结构体指针 + 动态内存 + 文件综合实战
c语言·开发语言·文件操作·结构体指针·动态内存·综合项目
cike_y7 分钟前
Java反序列化漏洞-Shiro721流程分析
java·反序列化·shiro框架
aq553560014 分钟前
三大编程语言深度对比:C# vs 易语言 vs 汇编
开发语言·汇编·c#
小贾要学习15 分钟前
【Linux】TCP网络通信编程
linux·服务器·网络·c++·网络协议·tcp/ip
独特的螺狮粉17 分钟前
云隙一言:鸿蒙Flutter框架 实现的随机名言应用
开发语言·flutter·华为·架构·开源·harmonyos
光泽雨20 分钟前
c# 文件编译的过程
开发语言·c#
极创信息29 分钟前
信创系统认证服务怎么做?从适配到验收全流程指南
java·大数据·运维·tomcat·健康医疗
格鸰爱童话35 分钟前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习
赤水无泪35 分钟前
09 C++ 11 新增的标准
开发语言