跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 异常处理:核心机制、自定义体系与实战

C++ 异常机制通过将错误检测与处理分离,实现了比 C 语言错误码更优雅的错误处理方式。文章详细讲解了异常的核心思想(throw/catch/try)、栈展开机制及匹配规则,重点介绍了自定义异常体系的设计方法以适配大型项目。此外,还涵盖了异常重新抛出、异常安全(资源泄漏防护)、noexcept 规范以及标准库异常体系等高级主题,并结合代码示例演示了实际应用,强调结合 RAII 机制确保异常安全性。

开源信徒发布于 2026/2/5更新于 2026/10/263K 浏览
C++ 异常处理:核心机制、自定义体系与实战

前言

在 C 语言中,我们通过错误码处理异常,但错误码只能返回简单状态,无法携带详细错误信息,且需要手动逐层检查,繁琐且易遗漏。C++ 的异常机制则彻底改变了这一现状 —— 它将'错误检测'与'错误处理'分离,允许程序在出错时抛出异常对象(携带完整错误信息),在合适的位置捕获并处理,让代码更优雅、逻辑更清晰。本文结合核心知识点和代码,从异常的基本语法、栈展开机制、捕获匹配规则,到异常安全、标准库异常体系,再到实战案例,全方位拆解 C++ 异常,帮你从'会用'到'用好',应对大型项目的错误处理需求。

一。异常的核心概念与基本语法

  • 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理,异常使得我们能够将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另一部分,检测环节无需知道问题的处理模块的所有细节
  • C 语言主要通过错误码的形式处理错误,错误码的本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。而异常是抛出一个对象,这个对象可以函数更全面的拿到各种信息。

1.1 异常的核心思想

  • 抛出(throw):程序遇到错误时,通过 throw 抛出一个异常对象(可以是任意类型,推荐自定义异常类);
  • 捕获(catch):通过 catch 语句捕获指定类型的异常,执行对应的处理逻辑;
  • try 块:try 包裹可能抛出异常的代码,后续紧跟一个或多个 catch 块,用于匹配异常

分析:

  • 程序出现问题时,我们通过抛出 (throw) 一个对象来引发一个异常,该对象的类型以及当前调用链决定了应该由哪个 catch 的处理代码来处理该异常。
  • 被选中的处理代码是调用链中与该对象类型匹配且抛出异常位置最近的那一个。根据抛出对象的类型和内容,程序的抛出异常部分告知异常处理部分到底发生了什么错误。
  • 当 throw 执行时,throw 后面的语句将不再被执行。程序的执行从 throw 位置跳到与之匹配的 catch 模块,catch 可能是同一函数中的一个局部的 catch,也可能是调用链中另一个函数的 catch,控制权从 throw 位置转移到了 catch 位置。这里还有两个重要的含义:1. 沿着调用链的函数可能提早结束退出。2. 一旦程序开始执行异常处理,沿着调用链创建的对象都将销毁。
  • 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,这个拷贝的对象会在 catch 子句后销毁。(这里的处理类似于函数的传值返回)

1.2 基础语法格式和最简示例

基本语法格式:

/*-----------------------------------------------------------------
try {
    // 可能抛出异常的代码
    可能出错的函数 ();
} catch (异常类型 1& e) {
    // 处理类型 1 异常
} catch (异常类型 2& e) {
    // 处理类型 2 异常
} catch (...) {
    // 捕获任意类型异常(兜底处理)
}
/*-----------------------------------------------------------------

最简示例(除零异常):

#include <iostream>
#include <exception>
#include <string>
using namespace std;

  {
    
     (b == ) {
         ();
    }  {
         (()a / ()b);
    }
}

{
     {
         len, time;
        cin >> len >> time;
        cout << (len, time) << endl;
    }  ( exception& e) {
        cout << e.() << endl;
    }
    cout <<  << __LINE__ << endl;
}

{
     () {
         {
            ();
        }  ( string& s) {
            cout << s << endl;
        }  ( exception& e) {
            cout << e.() << endl;
        }  (...) {
            
            cout <<  << endl;
        }
        cout <<  << __LINE__ << endl;
    }
     ;
}
double
Divide
(int a, int b)
// 当 b == 0 时抛出异常
if
0
throw
exception
"Divide by zero condition!"
else
return
double
double
void Func()
try
int
Divide
catch
const
what
"Func():"
int main()
while
1
try
Func
catch
const
catch
const
what
catch
// 任意类型的对象
"未知异常"
"Func():"
return
0

二。异常的核心机制:栈展开与匹配规则

2.1 栈展开

抛出异常后,程序会暂停当前函数执行,沿调用链向上查找匹配的 catch 块,这个过程称为'栈展开':

  1. 检查当前函数的 try/catch 块,若找到匹配的 catch,则执行处理逻辑;
  2. 若未找到,销毁当前函数的局部对象,退出当前函数,继续向上查找;
  3. 重复步骤 1-2,直到找到匹配的 catch;
  4. 若到达 main 函数仍未找到,调用 terminate 函数终止程序。

补充:

Stack Unwinding Diagram

Stack Unwinding Diagram 2

栈展开示例:

#include <iostream>
#include <string>
using namespace std;

void Func1() {
    throw "Func1 抛出异常"; // 抛出异常
}

void Func2() {
    Func1(); // 调用 Func1,不处理异常
}

void Func3() {
    Func2(); // 调用 Func2,不处理异常
}

int main() {
    try {
        Func3(); // 调用 Func3
    } catch (const char* errmsg) {
        // 捕获 Func1 抛出的异常(栈展开:Func1→Func2→Func3→main)
        cout << "捕获异常:" << errmsg << endl;
    }
    return 0;
}

2.2 异常捕获的匹配规则

捕获异常时,遵循'精确匹配优先、兼容转换次之'的原则:

  • 优先匹配与抛出对象类型完全一致的 catch;
  • 支持有限的类型转换:
    • 非常量→常量(int→const int);
    • 数组→数组元素指针(int[5]→int*);
    • 派生类→基类(最实用,用于自定义异常体系);
  • 若有多个 catch 块,按顺序匹配,匹配成功后不再检查后续 catch;
  • catch (...) 可捕获任意类型异常,通常作为兜底,避免程序终止。

补充:

Match Rules

三。自定义异常体系:大型项目的最佳实践

在大型项目中,直接抛出基本类型(如字符串、整数)的异常难以区分错误类型,推荐自定义异常类体系(基于继承),统一异常接口,便于管理和扩展。

3.1 自定义异常体系设计 && 异常抛出与捕获实战

核心思路:定义一个基类 Exception,派生类对应不同模块的异常(如 SQL 异常、缓存异常、HTTP 异常),通过多态返回详细错误信息。

代码实现:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cstdlib>
#include <ctime>
using namespace std;

// 一般大型项目程序才会使用异常,下面我们模拟设计一个服务的几个模块
// 每个模块的继承都是 Expection 的派生类,每个模块可以添加自己的数据
// 最后捕获的时候,我们捕获基类就可以,通过多态可以打印不同信息

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 += "->";
        str += _sql;
        return str;
    }
private:
    const string _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;
};

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;
}

部分输出演示:

Output Demo

四。异常的高级用法

4.1 异常重新抛出

有时捕获异常后,无法完全处理(如仅记录日志),或需要根据错误类型分流处理,可通过 throw; 重新抛出异常,让外层调用链继续处理。

示例:网络请求重试

// 下面程序模拟展示了聊天时发送消息,发送失败补货异常,但是可能在
// 电梯地下室等场景手机信号不好,则需要多次尝试
// 如果多次尝试都发送不出去,则就需要捕获异常再重新抛出,
// 其次如果不是网络差导致的错误,捕获后也要重新抛出。

void _SendMsg(const string& s) {
    if (rand() % 2 == 0) {
        throw HttpException("网络不稳定,发送失败", 102, "put");
    } else if (rand() % 7 == 0) {
        throw HttpException("你已经不是对方的好友,发送失败", 102, "put");
    } else {
        cout << "发送成功" << endl;
    }
}

// 网络不稳定,要求重试三次,均失败
void SendMsg(const string& s) {
    for (size_t i = 0; i < 4; i++) {
        try {
            _SendMsg(s); // 走到这里,如果没有抛异常导致结束
                         // 那就代表成功了,可以执行到这个 break,跳出循环
            break;
        } catch (const Exception& e) {
            if (e.getid() == 102) {
                if (i == 3) throw; // 重试三次失败,重新抛出
                cout << "开始第" << i + 1 << "重试" << endl;
            } else {
                // 重新抛出异常
                throw; // throw e; 也可以,但 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;
}

4.2 异常安全:避免资源泄漏

  • 异常抛出后,当前函数后续代码不再执行,若之前申请了资源(内存、锁、文件句柄),未及时释放会导致资源泄漏,这是异常使用的核心痛点。

解决方案:

  • 手动捕获释放:在 catch 中释放资源后重新抛出异常;
  • RAII 机制:利用类的构造 / 析构自动管理资源(推荐,如智能指针、自定义资源管理类);
  • 析构函数不抛异常:析构函数若抛出异常,可能导致资源释放不完全,需在析构函数内部捕获处理。

示例:

#include <iostream>
#include <cstdlib>
using namespace std;

double Divide(int a, int b) {
    // 当 b == 0 时抛出异常
    if (b == 0) {
        throw "Division by zero condition!";
    }
    return (double)a / (double)b;
}

void Func() {
    // 这里可以看到如果发生除 0 错误抛出异常,那下面的 array 就没有得到释放。
    // 所以这里捕获异常后并不处理异常,
    // 异常还是交给外层处理,这里捕获了再重新抛出去。
    int* array = new int[10];
    int len, time;
    cin >> len >> time;
    try {
        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 (...) {
        cout << "Unkown Exception" << endl;
    }
    return 0;
}

4.3 异常规范(noexcept)

C++11 提供 noexcept 关键字,用于声明函数是否会抛出异常,帮助编译器优化代码:

  • 函数声明 noexcept:表示函数不会抛出异常;
  • 函数声明 noexcept(表达式):表达式为 true 时,证明该函数不抛异常 (主要是用来确认和验证);
  • 若声明 noexcept 的函数实际抛出异常,程序会调用 terminate 终止(根本没有机会捕获)

补充:

Noexcept Diagram

实际示例:

#include <iostream>
using namespace std;

// C++11 标记不会抛异常的方法
// double Divide(int a, int b) noexcept
// C++98 用来标记会抛异常的方法
// double Divide(int a,int b) throw(const char*)
// C++98
// 这里表示这个函数只会抛出 bad_alloc 的异常
// void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
// void* operator delete (std::size_t size, void* ptr) throw();
// C++11
// size_type size() const noexcept;
// iterator begin() noexcept;
// const_iterator begin() const noexcept;

double Divide(int a, int b) noexcept {
    // 当 b == 0 时抛出异常
    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;
    cout << noexcept(Divide(1, 0)) << endl;
    cout << noexcept(++i) << endl;
    return 0;
}

五。C++ 标准库异常体系

C++ 标准库提供了一套预定义的异常继承体系,基类为 std::exception,派生类对应不同类型的标准异常(如内存分配失败、数组越界),可直接使用或继承扩展。

Standard Exception Hierarchy

标准库异常体系核心类:

异常类用途错误信息获取方式
std::exception所有标准异常的基类what()(虚函数)
std::bad_allocnew 分配内存失败时抛出what()返回'bad alloc'
std::out_of_range数组/容器越界时抛出what()返回越界信息
std::invalid_argument无效参数时抛出what()返回参数错误信息
  • 不过我们日常的话一般使用 std::exception 就 OK 了

结语

C++ 异常机制是大型项目错误处理的首选方案,它让错误处理逻辑与业务逻辑分离,代码更清晰、可维护。掌握异常的基本语法、栈展开机制、自定义异常体系和异常安全,能让你在应对复杂错误场景时游刃有余。实际开发中,建议结合 RAII 机制(如智能指针)解决资源泄漏问题,基于标准库 std::exception 扩展自定义异常,让异常处理既优雅又安全。

目录

  1. 前言
  2. 一。异常的核心概念与基本语法
  3. 1.1 异常的核心思想
  4. 1.2 基础语法格式和最简示例
  5. 二。异常的核心机制:栈展开与匹配规则
  6. 2.1 栈展开
  7. 2.2 异常捕获的匹配规则
  8. 三。自定义异常体系:大型项目的最佳实践
  9. 3.1 自定义异常体系设计 && 异常抛出与捕获实战
  10. 四。异常的高级用法
  11. 4.1 异常重新抛出
  12. 4.2 异常安全:避免资源泄漏
  13. 4.3 异常规范(noexcept)
  14. 五。C++ 标准库异常体系
  15. 结语

更多推荐文章

查看全部
  • AI 产品经理成长指南:核心能力与实战学习路线
  • Linux 系统下 libwebkit2gtk-4.1 安装与配置详解
  • MySQL 视图、用户与权限管理
  • 现代C++异步编程与序列化:yalantinglibs 库解析
  • Ratel 斗地主服务器部署与 cpolar 穿透实战
  • 网络安全入门教程:从零开始掌握基础技术与工具
  • 网络安全基础入门:概念、技术与工具
  • 网络安全入门:成为安全研究员需掌握的十大基础技能
  • Python 异步编程:asyncio 核心原理与实战
  • HarmonyOS 相机开发实战指南
  • DeepSeek 使用指南:提示词技巧与本地知识库搭建
  • OpenClaw 101:从零部署与实战指南
  • Windows 安装 OpenClaw 配置 Qwen 及 Ollama 本地模型接入飞书机器人
  • VS Code 配置 C/C++ 开发环境完整指南
  • Mac 系统 OpenClaw 本地 AI 执行引擎安装与配置指南
  • AI 辅助高水平学术论文写作:选题、写作、投稿与审稿全流程指南
  • 阿里通义千问 Qwen3-Coder:智能代码生成与代理式编程
  • WSL Ubuntu 22.04 国内镜像源设置教程(适配 ARM 开发)
  • Web 开发与设计全流程实战指南
  • Python 浏览器自动化:Playwright 结合 AI 的最佳实践

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online