C++异常处理

在C++开发中,程序运行时的非法运算、资源调用、接口请求等突发问题无法完全避免。早期C语言依靠错误码 处理异常,存在信息单一、排查繁琐、代码耦合度高的问题。而C++专属的异常处理机制 ,实现了业务逻辑与错误处理逻辑的解耦,能够精准传递错误详情、分层处理异常,是大型项目稳定性保障的核心语法。

本文将结合课件源码,系统拆解C++异常处理全知识点,每个知识点配套真实开发使用场景,兼顾原理、代码实战与工程落地,零基础也能轻松看懂。

一、C++异常核心概念(对比C语言错误码)

1.1 异常机制核心原理

C++异常处理机制的核心价值是分离问题检测与问题处理流程:程序某模块负责检测运行时错误并抛出异常,无需关心错误处理细节;另一专属模块负责捕获并处理异常,实现职责拆分。

相比于C语言错误码机制,优势极其明显:

  • C语言错误码:仅为数字编号,错误信息单一,获取错误码后需手动查表解析详情,代码冗余,跨层级传递困难。

  • C++异常机制 :抛出异常对象,可封装错误描述、错误编号、报错位置、关联参数等完整信息,支持跨函数、跨层级传递,排查效率大幅提升。

✅ 实战使用场景

适用于大型业务项目、服务端程序、接口开发、资源操作场景。例如数据库查询、网络请求、文件读写、数学运算(除零、越界)等大概率出现运行时错误的场景,替代繁琐的错误码判断,简化代码结构。小型工具类、高性能极简底层模块可酌情使用错误码。

二、异常的抛出与捕获(基础核心语法)

2.1 核心语法规则

C++异常处理依赖 try-throw-catch 三件套,核心执行逻辑:

  1. try:包裹可能出现异常的业务代码,监控代码运行状态;

  2. throw:检测到错误时,主动抛出异常对象,终止当前代码执行;

  3. catch:捕获匹配类型的异常对象,执行错误处理逻辑。

关键特性:throw执行后,后续代码立即终止,程序控制权直接跳转至匹配的catch模块;抛出的局部异常对象会生成拷贝,避免局部变量销毁导致的异常信息丢失,拷贝对象将在catch执行完毕后自动销毁。

2.2 基础实战代码

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 除法运算:检测除零异常并抛出
double Divide(int a, int b) {
    try {
        // 捕获除零非法运算
        if (b == 0) {
            string s("Divide by zero condition!");
            throw s; // 抛出string类型异常对象
        } else {
            return ((double)a / (double)b);
        }
    } catch (int errid) {
        // 仅捕获int类型异常(本次场景不匹配,仅作演示)
        cout << errid << endl;
    }
    return 0;
}

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

int main() {
    while (1) {
        try {
            Func();
        }
        // 精准捕获string类型异常
        catch (const string& errmsg) {
            cout << errmsg << endl;
        }
    }
    return 0;
}

2.3 代码核心解析

本次代码体现了异常跨层级捕获特性:Divide函数抛出string异常,Func函数的catch仅匹配char*类型,无法捕获,异常向上传递,最终由main函数的catch精准捕获并处理。

✅ 实战使用场景

适用于分层开发场景:底层工具函数(运算、IO、数据库操作)只负责检测错误、抛出异常,不处理业务报错;上层业务函数统一捕获异常,实现统一报错、日志记录、容错处理,保证底层代码纯粹性。

三、栈展开(异常传递核心机制)

3.1 原理详解

栈展开是C++异常传递的核心底层机制:当函数抛出异常后,若当前函数无匹配的catch处理器,系统会销毁当前函数栈帧、退出当前函数,向上逐层遍历函数调用链,寻找匹配的catch语句。

完整执行流程:

  1. 检测throw语句是否位于try块内,若无匹配catch,退出当前函数栈;

  2. 逐层向上遍历外层调用函数,重复匹配查找逻辑;

  3. 若遍历至main函数仍无匹配处理器,系统调用terminate函数终止整个程序。

经典调用链路示例:main()->func3()->func2()->func1(),func1抛出异常后,逐层销毁func1、func2、func3栈帧,最终由main函数捕获处理。

✅ 实战使用场景

用于多层嵌套调用的服务程序,例如接口转发、多级业务处理、链式函数调用。无需每一层函数都编写异常处理逻辑,仅需在顶层统一捕获,简化多层代码的异常处理逻辑,同时保证异常不丢失。

四、异常匹配规则(精准捕获机制)

4.1 基础匹配规则

默认情况下,异常捕获要求抛出类型与catch类型完全匹配 ;若存在多个匹配的catch,优先选择距离throw位置最近的处理器。

4.2 特殊兼容转换规则(工程高频使用)

  • 常量权限转换:非常量异常对象可被const引用类型catch捕获(权限缩小,保证异常对象不可修改,更安全);

  • 指针隐式转换:数组可转为元素类型指针、普通函数可转为函数指针(兼容C风格异常);

  • 继承多态转换:派生类异常可被基类catch捕获(自定义异常体系的核心原理)。

补充兜底方案:catch(...) 可捕获任意类型异常,作为程序最后一道防线,避免程序直接崩溃,但无法获取具体错误信息。

✅ 实战使用场景

继承转换规则是大型项目统一异常处理的核心:自定义各类业务异常(数据库、缓存、网络)并继承统一基类,顶层只需捕获基类异常,即可处理所有子业务异常,简化全局异常治理。catch(...)多用于main函数末尾,防止未知异常导致服务宕机。

五、自定义异常体系(工程级实战)

5.1 设计思想

C++标准异常仅提供基础错误信息,无法适配业务细分场景。实际开发中,我们会基于统一异常基类,派生业务专属异常类(数据库、缓存、网络请求等),封装专属错误字段,实现错误精细化区分、精准定位问题。

5.2 完整实战代码

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cstdlib>
#include <ctime>
using namespace std;

// 统一异常基类
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)
    {}

    // 重写虚函数:拼接数据库专属错误信息
    virtual string what() const
    {
        string str = "SqlException:";
        str += _errmsg;
        str += "->执行SQL:";
        str += _sql;
        return str;
    }

private:
    const string _sql; // 报错的SQL语句
};

// 缓存业务异常类
class CacheException : public Exception
{
public:
    CacheException(const string& errmsg, int id)
        :Exception(errmsg, id)
    {}

    virtual string what() const
    {
        string str = "CacheException:";
        str += _errmsg;
        return str;
    }
};

// 网络请求业务异常类
class HttpException : public Exception
{
public:
    HttpException(const string& errmsg, int id, const string& type)
        :Exception(errmsg, id), _type(type)
    {}

    virtual string what() const
    {
        string str = "HttpException:";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }

private:
    const string _type; // 请求方式:get/post/put
};

// 数据库业务模块
void SQLMgr()
{
    if (rand() % 7 == 0)
    {
        // 抛出数据库权限异常
        throw SqlException("权限不足", 100, "select * from name = '张三'");
    }
    else
    {
        cout << "SQLMgr 调用成功" << endl;
    }
}

// 缓存业务模块
void CacheMgr()
{
    if (rand() % 5 == 0)
    {
        throw CacheException("权限不足", 100);
    }
    else if (rand() % 6 == 0)
    {
        throw CacheException("数据不存在", 101);
    }
    else
    {
        cout << "CacheMgr 调用成功" << endl;
    }
    SQLMgr();
}

// 网络服务模块
void HttpServer()
{
    if (rand() % 3 == 0)
    {
        throw HttpException("请求资源不存在", 100, "get");
    }
    else if (rand() % 4 == 0)
    {
        throw HttpException("权限不足", 101, "post");
    }
    else
    {
        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 <<"Unkown Exception" << endl;
        }
    }
    return 0;
}

5.3 核心亮点解析

利用多态特性,通过基类Exception引用捕获所有派生类异常,无需为每个业务异常单独编写捕获逻辑,代码高度复用;每个自定义异常封装专属业务字段,报错信息精准可追溯。

✅ 实战使用场景

后端服务、分布式项目、模块化开发场景。将异常按业务模块拆分(数据库、缓存、网关、接口),报错时可直接定位故障模块,便于日志统计、异常监控、线上问题快速排查,是企业级项目的标准异常开发规范。

六、异常重新抛出(分层容错核心)

6.1 原理与语法

部分场景中,当前模块仅能处理特定类型异常 ,剩余异常需要交给上层业务处理,此时使用异常重新抛出 机制。语法极简:捕获异常后直接写throw;,无需携带参数,可完整传递原异常对象及所有信息,无信息丢失。

6.2 核心使用逻辑

  • 可自愈异常:当前模块重试、修复处理,无需上抛;

  • 不可自愈异常:直接重新抛出,交由顶层模块统一处理(日志、告警、返回前端报错)。

6.3 完整实战代码

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;

// 复用前文异常基类与HttpException类
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 HttpException : public Exception
{
public:
    HttpException(const string& errmsg, int id, const string& type)
        :Exception(errmsg, id), _type(type)
    {}
    virtual string what() const
    {
        string str = "HttpException:";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }
private:
    const string _type;
};

// 底层消息发送接口
void _SeedMsg(const string& s)
{
    if(rand()%2==0)
    {
        // 网络波动异常(可重试自愈)
        throw HttpException("网络不稳定,发送失败",102,"put");
    }
    else if(rand()%7==0)
    {
        // 业务权限异常(不可自愈)
        throw HttpException("你已经不是对象的好友,发送失败",103,"put");
    }
    else
    {
        cout<<"发送成功"<<endl;
    }
}

// 中层封装:消息重试逻辑
void SendMsg(const string& s)
{
    // 最多重试3次
    for (size_t i = 0; i < 4; i++)
    {
        try
        {
            _SeedMsg(s);
            break; // 发送成功,退出重试
        }
        catch (const Exception& e)
        {
            // 网络异常:重试自愈
            if (e.getid() == 102)
            {
                // 重试3次仍失败,上抛异常
                if (i == 3)
                    throw;
                cout << "开始第" << i + 1 << "次重试" << endl;
            }
            // 非网络异常:直接上抛,交由上层处理
            else
                throw;
        }
    }
}

int main()
{
    srand(time(0));
    string str;
    while (cin >> str)
    {
        try
        {
            SendMsg(str);
        }
        catch (const Exception& e)
        {
            cout << e.what() << endl << endl;
        }
        catch (...)
        {
            cout << "Unkown Exception" << endl;
        }
    }
    return 0;
}

✅ 实战使用场景

网络请求、消息推送、接口调用等可重试自愈场景。例如网络波动、瞬时超时等临时异常,本地重试修复;权限不足、参数错误等不可逆异常,直接上抛上层,统一返回报错信息、记录日志,兼顾容错性和业务规范性。

七、异常安全问题(避坑核心)

7.1 核心风险

异常抛出会终止当前代码执行 ,若函数中提前申请了堆内存、文件句柄、锁资源等,未执行释放代码就抛出异常,会直接导致资源泄漏,引发内存溢出、死锁、文件占用等严重问题。

重点禁忌:禁止析构函数抛出异常!若析构释放多个资源时中途抛异常,剩余资源无法释放,造成永久性资源泄漏。

7.2 安全解决方案

  1. 捕获异常后,优先手动释放已申请资源,再通过throw;重新抛出异常;

  2. 工程最优方案:使用RAII机制(智能指针、容器),依托对象生命周期自动管理资源,从根源避免泄漏。

7.3 安全实战代码

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

double Divide(int a, int b)
{
    if (b == 0)
    {
        throw "Division by zero condition!";
    }
    return (double)a / (double)b;
}

void Func()
{
    // 主动申请堆内存资源
    int* array = new int[10];
    try
    {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    }
    catch (...)
    {
        // 异常触发:优先释放资源,再上抛异常
        cout << "delete []" << array << endl;
        delete[] array;
        throw; // 重新抛出异常,交由上层处理
    }
    // 正常执行:主动释放资源
    cout << "delete []" << array << endl;
    delete[] array;
}

int main()
{
    try
    {
        Func();
    }
    catch (const char* errmsg)
    {
        cout << errmsg << endl;
    }
    catch (const exception& e)
    {
        cout << e.what() << endl;
    }
    catch (...)
    {
        cout << "Unkown Exception" << endl;
    }
    return 0;
}

✅ 实战使用场景

所有涉及手动资源申请的场景:动态内存分配、文件打开、互斥锁加锁、数据库连接申请等。必须做好异常安全防护,避免长期运行的服务程序因资源泄漏逐渐崩溃。

八、异常规范(C++98/C++11语法迭代)

8.1 语法迭代与规则

异常规范用于声明函数的异常抛出能力,方便开发者预判函数风险,简化调用逻辑,C++标准对其做了大幅优化:

  • C++98 老旧规范throw() 表示不抛异常;throw(类型1,类型2) 表示仅抛出指定类型异常,语法繁琐、实用性差;

  • C++11 全新规范 :简化为 noexcept,修饰函数表示绝对不抛出异常;无修饰则默认可抛异常;

  • 补充特性:noexcept(表达式) 可作为运算符,检测表达式是否会抛异常,返回布尔值。

核心注意:编译器不强制校验noexcept,若noexcept修饰的函数违规抛异常,程序会直接调用terminate终止运行。

8.2 实战代码

cpp 复制代码
#include <iostream>
using namespace std;

// C++11 noexcept修饰:声明函数不抛异常(实际违规抛异常)
double Divide(int a, int b) noexcept
{
    if (b == 0)
    {
        throw "Division by zero condition!";
    }
    return (double)a / (double)b;
}

int main()
{
    try
    {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    }
    catch (const char* errmsg)
    {
        cout << errmsg << endl;
    }
    catch (...)
    {
        cout << "Unkown Exception" << endl;
    }

    // 检测表达式是否会抛出异常
    int i = 0;
    cout << noexcept(Divide(1,2)) << endl;  // 无异常:true(1)
    cout << noexcept(Divide(1,0)) << endl;  // 有异常:false(0)
    cout << noexcept(++i) << endl;          // 无异常:true(1)

    return 0;
}

✅ 实战使用场景

用于底层工具函数、STL源码、高性能模块。对于确定不会出错的基础函数、容器接口、数学工具函数,添加noexcept修饰,帮助编译器优化代码,提升程序运行效率,同时规范函数行为。

九、C++标准库异常体系

C++标准库内置完整的异常继承体系,所有标准异常的基类为std::exception ,提供核心虚函数what(),用于返回异常详情,派生类可重写该函数自定义报错信息。

开发规范:自定义异常建议继承std::exception基类,统一全局异常体系,便于顶层统一捕获、统一处理,兼容标准库异常,避免异常体系混乱。

十、总结:C++异常工程最佳实践

  1. 逻辑解耦:业务逻辑与错误处理分离,代码更整洁、易维护;

  2. 分层处理:底层抛异常、中层容错重试、顶层统一捕获处理;

  3. 规范继承:自定义异常继承std::exception,搭建模块化异常体系;

  4. 安全优先:杜绝析构抛异常,异常场景必释放资源,优先使用RAII;

  5. 合理规范:稳定底层函数添加noexcept,优化性能、规范接口。

相关推荐
Dovis(誓平步青云)1 小时前
《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——HTML文件的清洗(上篇)》
开发语言·数据库·c++·搜索引擎·开源
布朗克1681 小时前
Go 入门到精通-13-指针与内存
开发语言·c++·golang·指针与内存
问君能有几多愁~9 小时前
C++ 数据结构复习笔记
数据结构·c++·笔记
tntxia10 小时前
C++ 基础教程:从入门到精通
c++
Drone_xjw10 小时前
从 GDB 到 CDB:C/C++ 程序调试的两把“手术刀”
c语言·开发语言·c++
熊猫_豆豆13 小时前
QT6 Android C++ 自制美观闹钟
android·c++·qt·闹钟
随意起个昵称14 小时前
状压dp-基础题目2([USACO12MAR] Cows in a Skyscraper G)
c++·算法·动态规划
无限的鲜花15 小时前
协程本质是函数加状态机——零基础深入浅出 C++20 协程
c++·算法·c++20
精明的身影15 小时前
C++自学之路1:Hello world
开发语言·c++