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

相关推荐
我命由我123459 分钟前
Kotlin 开发 - lateinit 关键字
android·java·开发语言·kotlin·android studio·android-studio·android runtime
aXin_ya11 分钟前
微服务第八天 Sentinel 四种分布式事务模式
java·数据库·微服务
智者知已应修善业12 分钟前
【51单片机2个按键控制流水灯运行与暂停】2023-9-6
c++·经验分享·笔记·算法·51单片机
Halo_tjn14 分钟前
Java Set集合相关知识点
java·开发语言·算法
Linsk19 分钟前
Java和JavaScript的关系真是雷峰和雷峰塔的关系吗?
java·javascript·oracle
许彰午32 分钟前
我手写了一个 Java 内存数据库(二):B+ 树的插入与分裂
java·开发语言·面试
zhouwy11333 分钟前
Java 快速入门笔记:从基础语法到 Spring Boot 实战
java
大飞记Python1 小时前
【2026更新】Python基础学习指南(AI版)——04数据类型
开发语言·人工智能·python
极创信息1 小时前
信创产品认证怎么做?信创产品测试认证的主要流程
java·大数据·数据库·金融·软件工程
SamDeepThinking1 小时前
并发量就算只有2,该上锁还得上呀
java·后端·架构