前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C++进阶篇】走进C++的异常世界:从抛出到捕获的全方位解读

【C++进阶篇】走进C++的异常世界:从抛出到捕获的全方位解读

作者头像
熬夜学编程的小王
发布于 2025-05-21 04:44:09
发布于 2025-05-21 04:44:09
20300
代码可运行
举报
文章被收录于专栏:编程小王编程小王
运行总次数:0
代码可运行
C++异常处理全攻略:捕获、栈展开与重新抛出

一. 异常

1.1 异常概念

在编程中,异常(Exception)是指在程序执行过程中出现的错误或意外情况,它会导致程序的正常流程中断,程序控制会转移到一个专门处理错误的区域。异常处理机制的主要目的是让程序在遇到错误时能够优雅地退出,或者做出相应的处理,避免程序崩溃。

1.2 异常的抛出和捕获

  • 下面通过一个代码来演示该过程:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdexcept>  // 包含标准异常类

using namespace std;

// 自定义异常类(继承自exception)
class NegativeValueError : public exception {
public:
    const char* what() const noexcept override {
        return "错误:不允许负值参数";
    }
};

// 可能抛出异常的函数
double calculate_sqrt(double value) {
    if (value < 0) {
        throw NegativeValueError();  // 抛出自定义异常
    }
    return sqrt(value);
}

// 另一个可能抛出异常的函数
int divide_numbers(int a, int b) {
    if (b == 0) {
        throw runtime_error("错误:除数不能为零");  // 抛出标准异常
    }
    if (a % b != 0) {
        throw "结果不是整数";  // 抛出原始字符串(不推荐,仅作演示)
    }
    return a / b;
}

int main() {
    try {
        // 测试平方根计算
        cout << "计算sqrt(4): ";
        cout << calculate_sqrt(4) << endl;
        
        cout << "计算sqrt(-1): ";
        cout << calculate_sqrt(-1) << endl;  // 这里会抛出异常

        // 测试除法运算
        cout << "\n10 / 2 = ";
        cout << divide_numbers(10, 2) << endl;
        
        cout << "5 / 0 = ";
        cout << divide_numbers(5, 0) << endl;  // 这里会抛出异常
        
        cout << "7 / 3 = ";
        cout << divide_numbers(7, 3) << endl;  // 这里会抛出异常

    } catch (const NegativeValueError& e) {  // 捕获自定义异常
        cerr << "捕获到自定义异常: " << e.what() << endl;
    } catch (const runtime_error& e) {       // 捕获标准异常
        cerr << "捕获到运行时错误: " << e.what() << endl;
    } catch (const char* msg) {              // 捕获原始字符串异常
        cerr << "捕获到字符串异常: " << msg << endl;
    } catch (...) {                          // 捕获所有其他异常
        cerr << "捕获到未知异常" << endl;
    }

    cout << "\n程序继续执行..." << endl;
    return 0;
}

输出结果:

计算sqrt(4): 2 计算sqrt(-1): 捕获到自定义异常: 错误:不允许负值参数 程序继续执行…

  1. 从该语句cout << calculate_sqrt(-1) << endl; 抛出(throw)异常后,后续代码不再继续执行,跳出try{}catch{}的作用域。
  2. 通过catch捕获抛出的异常,并对其进行处理。
  3. 抛出异常后,离抛出异常最近的那个类型对其进行处理。
  4. 抛出异常对象后,会生成一个异常对象的临时拷贝,catch执行完后会自动销毁它。

1.3 栈展开

栈展开(Stack Unwinding)是 C++ 异常处理机制中的核心概念之一。当异常被抛出且未被当前作用域捕获时,程序会沿着调用链逐层销毁局部对象(即栈上的对象),直到找到匹配的 catch 块为止。这个过程称为栈展开。

  • 下面通过一个代码来总结一些特点:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass(int id) : id_(id) {
        cout << "对象 " << id_ << " 构造" << endl;
    }
    ~MyClass() {
        cout << "对象 " << id_ << " 析构" << endl;
    }
private:
    int id_;
};

void func3() {
    MyClass obj3(3);  // 局部对象
    throw runtime_error("异常发生!");
}

void func2() {
    MyClass obj2(2);  // 局部对象
    func3();
}

void func1() {
    MyClass obj1(1);  // 局部对象
    func2();
}

int main() {
    try {
        func1();
    } catch (const exception& e) {
        cerr << "捕获异常: " << e.what() << endl;
    }
    cout << "程序继续执行..." << endl;
    return 0;
}

输出结果:

对象 1 构造 对象 2 构造 对象 3 构造 对象 3 析构 对象 2 析构 对象 1 析构 捕获异常: 异常发生! 程序继续执行…

  • 详细过程:
  1. 抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch语句,首先检查throw本身是否在try内部,如果在则寻找与之匹配的catch语句,若有则跳到catch的地方进行处理。
  2. 如果当前函数没有try/catch语句,或者有但类型不匹配,则退出当前函数,继续在调用该函数的函数中进行查找,这个过程成为栈展开。
  3. 如果到main函数之前还没有,程序会调用terminate终止程序运行。
  4. 如果找到,catch匹配的代码继续运行。
1.3.1 查找匹配的处理代码
  • 如果有多个与抛出异常类型相同的catch语句,选择与栈展开过程与之最近的一个执行。
  • 特殊情况:允许从非常量向常量的类型转换,即权限缩小;允许数组转换成指向数组元素类型的指针,函数转换成指向函数指针;允许派生类向基类进行转换,在继承体系中司空见惯。
  • 抛出异常后如果到达main函数,我们是不希望程序终止的,所以一般main函数会使用catch(…),它可以捕获任意类型的异常,但不知道异常具体是什么。

上述文字说的很抽象,通过代码来讲解上述枯燥的文字。

  • 示例代码:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdexcept>
using namespace std;

// 派生类转基类示例
class Base { virtual void foo() {} };
class Derived : public Base {};

// 自定义异常类
class MyException : public exception {
public:
    const char* what() const noexcept override {
        return "自定义异常:派生类转基类失败";
    }
};

int main() {
    try {
        // 类型转换示例 1:非常量转常量
        int num = 42;
        const int& cnum = num;

        // 类型转换示例 2:数组转指针
        int arr[5] = { 1,2,3,4,5 };
        int* ptr = arr;

        // 类型转换示例 3:函数转指针
        void (*funcPtr)(int) = [](int x) { cout << "Lambda: " << x << endl; };
        funcPtr(10);

        // 类型转换示例 4:派生类转基类
        Derived d;
        Base* b = &d;

        // 异常抛出示例
        if (b == nullptr) throw MyException();
        throw runtime_error("运行时错误:空指针异常");

    }
    catch (const MyException& e) {  // 精确捕获
        cerr << "捕获特定异常: " << e.what() << endl;
    }
    catch (const exception& e) {    // 标准异常捕获
        cerr << "捕获标准异常: " << e.what() << endl;
    }
    catch (...) {                   // 兜底捕获(无法获取具体信息)
        cerr << "捕获未知异常(无法获取详细信息)" << endl;
    }

    return 0;
}

exception是所有异常的基类,可以捕获任何异常。 输出结果:

Lambda: 10 捕获标准异常: 运行时错误:空指针异常

1.4 异常重新抛出

异常重新抛出(Re-throwing Exceptions)是 C++ 异常处理中的重要机制,允许在捕获异常后将其原样或修改后再次抛出。 语法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
    // 可能抛出异常的代码
} catch (const SomeException& e) {
    // 预处理逻辑(如记录日志)
    throw;  // 重新抛出原始异常(保持类型不变)
}

示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdexcept>
using namespace std;

void process_data(int value) {
    if (value < 0) {
        throw invalid_argument("值不能为负数");
    }
    // 处理数据...
}

int main() {
    try {
        process_data(-5);//发生异常
    } catch (const exception& e) {//处理异常
        cerr << "底层捕获异常: " << e.what() << endl;
        
        // 添加额外处理逻辑(如记录日志)
        // throw;  // 重新抛出原始异常(类型保持为 invalid_argument)
        
        // 或者抛出新异常(类型改变)
        throw runtime_error("数据预处理失败: " + string(e.what()));//异常重新抛出
    }
    return 0;
}

输出结果:

底层捕获异常: 值不能为负数 terminate called after throwing an instance of ‘std::runtime_error’ what(): 数据预处理失败: 值不能为负数

1.5 异常安全

抛出异常后,可能会导致资源没有机会被正确回收,从而导致内存泄漏。RAII模式可以完美解决该问题。析构函数抛出异常,也需谨慎处理。

1.6 异常规范

  1. 对于⽤⼾和编译器⽽⾔,预先知道某个程序会不会抛出异常⼤有裨益,知道某个函数是否会抛出异常有助于简化调⽤函数的代码。
  2. C++98中函数参数列表的后⾯接throw(),表⽰函数不抛异常,函数参数列表的后⾯接throw(类型1,类型2…)表⽰可能会抛出多种类型的异常,可能会抛出的类型⽤逗号分割。该设计模式过于复杂,C++11使用noexcept表示不会抛出异常。
  3. 如果一个函数已经抛出异常,而这个函数又被noexcept修饰,程序调用terminate终止程序。
  4. noexcept(expression)还可以作为⼀个运算符去检测⼀个表达式是否会抛出异常,可能会则返回false,不会就返回true。

二. 标准库异常

C++标准库也定义了⼀套⾃⼰的⼀套异常继承体系库,基类是exception,所以我们⽇常写程序,需要在主函数捕获exception即可,要获取异常信息,调⽤what函数,what是⼀个虚函数,派⽣类可以重写。通过基类指针或引用实现多态行为。

三. 最后

本文系统讲解了C++异常处理机制,涵盖异常概念、抛出捕获、栈展开、异常重新抛出、异常安全及标准异常体系等内容。通过代码示例演示了自定义异常、类型转换异常、资源管理等核心场景,强调RAII模式在异常安全中的关键作用,并对比了C++不同版本异常规范的演变。掌握这些机制可编写更健壮的C++程序,有效处理运行时错误,避免资源泄漏

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【探寻C++之旅】第十二章:异常
异常是面向对象语言在处理错误的一种方式。在C语言中我们主要通过错误码的形式处理错误,例如我们可以通过perror函数去打印对应的错误码所指代的信息:
code_monnkey_
2025/05/31
850
【探寻C++之旅】第十二章:异常
C++ 异常处理机制详解:从基础语法到工程实践
在 C++ 中,异常处理是一种重要的错误管理机制,用于捕获程序运行时出现的问题并优雅地进行处理。相比传统的错误返回码,异常提供了结构化、清晰的处理方式,使代码逻辑更清晰、可维护性更强。
用户11690571
2025/06/06
100
C++:异常的捕获和处理
        设想这样的场景,假设我们在看抖音的直播,这个页面有非常多的功能——>对应的不同的按键底层对应会调用不同的函数,比如说给主播刷礼物、给主播点赞点关注、和主播聊天、退出直播…… 在直播画面的运行过程中,画面一直是处在一个循环的过程中的,而我们想要去结束这个循环,就点点击退出直播的按钮,这个时候就可以跳出直播的这个页面。这个是非常合理的,但是除此之外的其他模块如果发生了错误,难道也要终止程序么??
小陈在拼命
2024/05/26
3390
C++:异常的捕获和处理
项目中你会用C++异常处理吗?
在 C++ 中,异常处理是一种用于处理程序运行过程中发生的错误或异常情况的机制。当程序出现异常情况时,可以使用异常处理机制来捕获、传递和处理异常,以保证程序的稳定性和可靠性。
Linux兵工厂
2024/04/01
2390
项目中你会用C++异常处理吗?
C++异常处理完全指南:从原理到实战
异常是程序运行时发生的非预期事件(如除零、内存不足)。C++通过try、catch和throw提供结构化处理机制,使程序能够优雅处理错误,而非直接崩溃。
用户11286421
2025/03/23
1030
C++异常处理完全指南:从原理到实战
【C++】你了解异常的用法吗?
实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。
利刃大大
2025/05/21
1060
【C++】你了解异常的用法吗?
【C++】异常
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的 直接或间接的调用者处理这个错误。
秦jh
2024/12/24
1590
【C++】异常
C++异常处理深度探索:从基础概念到高级实践策略
在现代编程实践中,异常处理是一项至关重要的技能,特别是在开发复杂和大型系统时。C++作为一种强大而灵活的编程语言,提供了丰富的异常处理机制,使得开发者能够有效地管理运行时错误和异常情况。本文旨在深入探讨C++中的异常处理机制,从基本的语法结构到实际的应用场景,帮助读者掌握这一关键技能。 本文将从C++异常处理的基本概念出发,逐步介绍如何定义和抛出异常、如何捕获和处理异常,以及如何在复杂项目中有效运用异常处理机制。此外,我们还将讨论一些常见的异常处理策略和最佳实践,帮助读者避免常见陷阱,写出更加健壮和可靠的C++代码。
suye
2024/10/24
3230
【c++】c++异常&&c++的异常处理详解
实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误
用户10925563
2024/08/06
2500
【c++】c++异常&&c++的异常处理详解
C++缝隙间的重构史诗:异常
通过合理的异常处理机制,程序可以在遇到异常情况时,避免直接崩溃,而是采取合适的措施,如提示用户错误信息、进行数据回滚、尝试重新执行操作等,从而增强程序的健壮性和稳定性
DARLING Zero two
2025/05/06
880
C++缝隙间的重构史诗:异常
【C++】异常
异常处理机制允许程序中独⽴开发的部分能够在运⾏时就出现的问题进⾏通信并做出相应的处理, 异常使得我们能够将问题的检测与解决问题的过程分开,程序的⼀部分负责检测问题的出现,然后 解决问题的任务传递给程序的另⼀部分,检测环节⽆须知道问题的处理模块的所有细节。
用户11375356
2025/02/13
1190
【C++】异常
【C++进阶学习】第十二弹——C++ 异常处理:深入解析与实践应用
1. 异常是通过抛出对象来激活的,该对象的类型决定了应该激活那个catch的处理代码
GG Bond1
2024/08/09
1800
【C++进阶学习】第十二弹——C++ 异常处理:深入解析与实践应用
【C++高阶】:异常详解
🧩·C++针对上面的不足,引入了异常的概念,不会终止程序,并且会将错误信息详细介绍。
IsLand1314
2024/10/15
1780
【C++高阶】:异常详解
C++异常处理:提高代码健壮性和可维护性
异常处理是一种重要的编程技术,它可以帮助我们提高C++代码的健壮性和可维护性。通过合理地处理异常,我们可以使程序在面对错误和异常情况时更加稳定,并且能够更好地定位和解决问题。本文将介绍C++中的异常处理机制,并分享一些异常处理的最佳实践。
大盘鸡拌面
2023/12/01
3730
【C++】异常处理 ⑧ ( 标准异常类 | 标准异常类继承结构 | 常用的标准异常类 | 自定义异常类继承 std::exception 基类 )
在 标准库 中 , 抛出的异常 , 都是 标准异常类 , 都是 std::exception 类的子类 ;
韩曙亮
2023/12/05
8750
【C++】异常处理 ⑧ ( 标准异常类 | 标准异常类继承结构 | 常用的标准异常类 | 自定义异常类继承 std::exception 基类 )
C++:异常
实际中 C 语言基本都是使用返回 错误码 的方式处理错误,部分情况下使用终止程序处理非常严重的
啊QQQQQ
2024/11/19
1120
C++:异常
【c++】异常
异常是处理错误的一种方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数直接或间接的调用者处理这个错误
用户11029103
2025/02/07
1650
【c++】异常
【C++】异常
异常处理机制允许程序中独⽴开发的部分能够在运⾏时就出现的问题进⾏通信并做出相应的处理,
用户11290673
2025/02/10
1240
【C++】异常
C++ —— 拨乱反正 回归世界的真理 —异常
try:try中包含会出现异常的代码或者函数,后面通常会跟一个或者多个catch块
迷迭所归处
2024/11/25
1510
C++ —— 拨乱反正 回归世界的真理 —异常
C++中的栈展开:实现机制及其目的
栈展开是C++异常处理机制的重要部分,它主要负责在抛出异常时正确地释放资源。在深入探讨这个概念之前,让我们先理解一下什么是栈。
进击的可乐
2024/06/16
4600
C++中的栈展开:实现机制及其目的
相关推荐
【探寻C++之旅】第十二章:异常
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验