第三章 异常(一)

第三章 异常(一)

条款9:利用destructors避免泄露资源

一、核心概念解析

首先,我们要理解这个条款解决的核心问题:手动管理资源(如内存、文件句柄、网络连接等)时,容易因忘记释放、程序提前退出(如异常)等原因导致资源泄露

C++ 的析构函数(destructor)有一个关键特性:当一个对象的生命周期结束(如离开作用域、被 delete)时,其析构函数会自动调用。利用这个特性,我们可以将资源的释放逻辑封装到析构函数中,让资源的生命周期与对象绑定 —— 这就是 RAII(Resource Acquisition Is Initialization,资源获取即初始化)的核心思想。

问题场景:手动管理资源的风险

先看一个反例,直观感受资源泄露的问题:

#include <iostream> #include <string> // 模拟一个需要手动释放的资源(如动态内存) void createResource(std::string*& ptr) { ptr = new std::string("我是需要释放的资源"); } void releaseResource(std::string* ptr) { delete ptr; ptr = nullptr; } void riskyFunction(bool throwException) { std::string* res = nullptr; createResource(res); // 获取资源 // 模拟业务逻辑:如果抛出异常,后续的releaseResource不会执行 if (throwException) { throw std::runtime_error("业务逻辑异常"); } // 即使没有异常,也可能忘记写这行,导致内存泄露 releaseResource(res); } int main() { try { riskyFunction(true); // 传入true触发异常,资源泄露 } catch (const std::exception& e) { std::cout << "捕获异常:" << e.what() << std::endl; } // 程序结束后,res指向的内存未被释放,发生泄露 return 0; }

问题分析

1.如果riskyFunction中抛出异常,releaseResource不会执行,资源泄露;

2.即使没有异常,手动调用releaseResource容易遗漏,导致泄露;

3.代码需要手动配对 “获取 - 释放”,心智负担重。

二、解决方案:用析构函数自动释放资源

我们可以封装一个资源管理类,在构造函数中获取资源,析构函数中释放资源。只要这个类的对象离开作用域,析构函数就会自动调用,资源被释放,从根本上避免泄露。

代码示例:实现一个简单的资源管理类
#include <iostream> #include <string> #include <stdexcept> // 资源管理类:遵循RAII原则 class ResourceGuard { private: std::string* m_resource; // 管理的资源(这里以动态字符串为例) public: // 构造函数:获取资源(资源获取即初始化) explicit ResourceGuard(const std::string& content) : m_resource(new std::string(content)) { std::cout << "资源已获取,地址:" << m_resource << std::endl; } // 析构函数:自动释放资源(无论正常退出还是异常退出) ~ResourceGuard() { if (m_resource != nullptr) { delete m_resource; m_resource = nullptr; std::cout << "资源已释放" << std::endl; } } // 禁用拷贝构造和拷贝赋值(避免浅拷贝导致重复释放) ResourceGuard(const ResourceGuard&) = delete; ResourceGuard& operator=(const ResourceGuard&) = delete; // 提供访问资源的接口(可选) std::string& getResource() const { if (m_resource == nullptr) { throw std::runtime_error("资源已释放"); } return *m_resource; } }; // 安全的函数:使用资源管理类 void safeFunction(bool throwException) { // 创建资源管理对象,构造函数获取资源 ResourceGuard guard("我是受保护的资源"); // 模拟业务逻辑:即使抛出异常,guard的析构函数仍会执行 if (throwException) { throw std::runtime_error("业务逻辑异常,但资源不会泄露"); } // 正常使用资源 std::cout << "资源内容:" << guard.getResource() << std::endl; // 函数结束时,guard离开作用域,析构函数自动释放资源 } int main() { try { safeFunction(true); // 触发异常 } catch (const std::exception& e) { std::cout << "捕获异常:" << e.what() << std::endl; } std::cout << "程序正常结束" << std::endl; return 0; }
关键细节解释:

1.RAII 核心ResourceGuard的构造函数负责 “获取资源”,析构函数负责 “释放资源”,资源的生命周期与guard对象绑定;

2.异常安全:即使safeFunction抛出异常,guard对象的析构函数仍会被调用(C++ 保证栈上对象的析构函数在异常展开时执行),资源不会泄露;

3.禁用拷贝:如果允许拷贝,多个ResourceGuard对象会管理同一份资源,析构时会重复释放导致崩溃,因此禁用拷贝构造和拷贝赋值(C++11 后也可使用移动语义);

4.通用性:这个思路不仅适用于内存,还适用于文件句柄、锁、网络连接等所有需要手动释放的资源(比如std::fstream自动关闭文件、std::lock_guard自动释放锁,都是这个原理)。

进阶:使用标准库的智能指针(更推荐)

实际开发中,我们不需要自己写资源管理类,C++ 标准库提供了现成的智能指针(std::unique_ptr/std::shared_ptr),它们的底层就是利用析构函数自动释放资源:

#include <iostream> #include <string> #include <memory> // 包含智能指针头文件 #include <stdexcept> void smarterFunction(bool throwException) { // std::unique_ptr:独占式智能指针,析构时自动delete std::unique_ptr<std::string> res = std::make_unique<std::string>("智能指针管理的资源"); if (throwException) { throw std::runtime_error("异常发生,但智能指针会自动释放资源"); } std::cout << "资源内容:" << *res << std::endl; } int main() { try { smarterFunction(true); } catch (const std::exception& e) { std::cout << "捕获异常:" << e.what() << std::endl; } return 0; }

std::unique_ptr是条款 9 的最佳实践落地 —— 它完全遵循 RAII,无需手动管理,且性能几乎与裸指针一致。

总结

  1. 核心思想:将资源的释放逻辑封装到析构函数中,利用析构函数 “自动调用” 的特性,避免手动释放资源的遗漏或异常导致的泄露(RAII 原则);
  2. 关键做法:不要直接管理裸资源,而是用对象(如自定义资源管理类、标准库智能指针)包裹资源,让对象的生命周期与资源绑定;
  3. 实践推荐:优先使用 C++ 标准库提供的智能指针(std::unique_ptr/std::shared_ptr),而非手写资源管理类,避免重复造轮子且更安全。

条款10:在constructors内阻止资源泄露(resource leak)

一、核心问题:构造函数的特殊性

C++ 的构造函数没有返回值,且如果在构造过程中抛出异常,当前对象的析构函数不会被调用。这意味着:如果构造函数中分配了资源(如动态内存、文件句柄、锁、网络连接等),但在资源分配后、构造完成前抛出了异常,这些已分配的资源就无法被析构函数释放,从而导致资源泄露

二、解决方案:RAII(资源获取即初始化)

条款 10 的核心解决方案是RAII(Resource Acquisition Is Initialization):将资源的生命周期绑定到对象的生命周期 —— 资源在对象构造时获取,在对象析构时释放。具体来说:

1.把资源封装到独立的 “资源管理类” 中;

2.在构造函数中只创建资源管理类的对象,而非直接操作裸资源;

3.即使构造函数抛出异常,资源管理类的析构函数仍会被调用,从而保证资源释放。

三、代码示例:反例(有资源泄露)+ 正例(无泄露)

#include <iostream> #include <stdexcept> using namespace std; // 模拟一个需要手动释放的资源(如动态内存、文件句柄) class Resource { public: Resource() { cout << "Resource 分配成功\n"; } ~Resource() { cout << "Resource 释放成功\n"; } // 析构释放资源 void use() const { /* 资源使用逻辑 */ } }; // 有资源泄露风险的类 class BadClass { private: Resource* res1; // 裸指针管理资源1 Resource* res2; // 裸指针管理资源2 public: BadClass() { // 第一步:分配资源1(成功) res1 = new Resource(); // 第二步:模拟构造过程中抛出异常(比如资源2分配失败、逻辑错误) throw runtime_error("构造函数执行中发生异常"); // 第三步:分配资源2(永远不会执行) res2 = new Resource(); } ~BadClass() { // 析构函数不会被调用!因为构造函数抛异常,对象未完全构造 delete res1; delete res2; cout << "BadClass 析构,释放所有资源\n"; } }; int main() { try { BadClass obj; // 构造时抛异常 } catch (const exception& e) { cout << "捕获异常:" << e.what() << endl; } // 输出:Resource 分配成功 → 捕获异常 → 无"Resource 释放成功" // 结论:res1的资源永远无法释放,造成泄露 return 0; }
2. 正例:用 RAII 封装资源(解决泄露)

核心思路:用智能指针(如 std::unique_ptr)替代裸指针 —— 智能指针是 RAII 的典型实现,其析构函数会自动释放管理的资源,即使构造函数抛异常。

#include <iostream> #include <stdexcept> #include <memory> // 包含智能指针头文件 using namespace std; // 待管理的资源(同上) class Resource { public: Resource() { cout << "Resource 分配成功\n"; } ~Resource() { cout << "Resource 释放成功\n"; } void use() const { /* 资源使用逻辑 */ } }; // 安全的类:用RAII(智能指针)管理资源 class GoodClass { private: // 用std::unique_ptr(独占所有权)替代裸指针,自动管理资源 unique_ptr<Resource> res1; unique_ptr<Resource> res2; public: GoodClass() { // 第一步:分配资源1(封装到unique_ptr中) res1 = make_unique<Resource>(); // C++14及以上,等价于 unique_ptr<Resource>(new Resource()) // 第二步:模拟构造过程中抛异常 throw runtime_error("构造函数执行中发生异常"); // 第三步:分配资源2(不会执行) res2 = make_unique<Resource>(); } ~GoodClass() { // 即使析构函数不手动释放,unique_ptr也会自动释放资源 cout << "GoodClass 析构\n"; } }; int main() { try { GoodClass obj; // 构造时抛异常 } catch (const exception& e) { cout << "捕获异常:" << e.what() << endl; } // 输出:Resource 分配成功 → 捕获异常 → Resource 释放成功 // 结论:res1的资源被unique_ptr的析构函数自动释放,无泄露 return 0; }

3. 扩展:自定义 RAII 资源管理类(理解底层原理)

如果需要管理非内存资源(如文件句柄、锁),可以自定义 RAII 类:

#include <iostream> #include <stdexcept> #include <cstdio> // FILE相关头文件 using namespace std; // 自定义RAII类:管理文件句柄(非内存资源) class FileHandle { private: FILE* file; // 裸句柄(仅在RAII类内部使用) public: // 构造:获取资源(打开文件) FileHandle(const char* filename, const char* mode) { file = fopen(filename, mode); if (!file) { throw runtime_error("文件打开失败"); } cout << "文件 " << filename << " 打开成功\n"; } // 析构:释放资源(关闭文件) ~FileHandle() { if (file) { fclose(file); cout << "文件关闭成功\n"; } } // 禁用拷贝(避免资源重复释放) FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; // 提供资源访问接口 FILE* get() const { return file; } }; // 使用自定义RAII类的业务类 class FileProcessor { private: FileHandle fh; // RAII对象,绑定文件资源 public: FileProcessor(const char* filename) : fh(filename, "w") { // 模拟构造过程中抛异常 throw runtime_error("FileProcessor构造异常"); } }; int main() { try { FileProcessor fp("test.txt"); } catch (const exception& e) { cout << "捕获异常:" << e.what() << endl; } // 输出:文件 test.txt 打开成功 → 捕获异常 → 文件关闭成功 // 结论:即使构造抛异常,FileHandle的析构仍会执行,文件句柄无泄露 return 0; }

总结

  1. 核心风险:构造函数抛异常时,对象未完全构造,析构函数不会执行,直接管理的裸资源会泄露;
  2. 核心方案:采用RAII思想,将资源封装到 “资源管理类” 中(如 std::unique_ptr、std::shared_ptr,或自定义 RAII 类),利用资源管理类的析构函数自动释放资源;
  3. 最佳实践:在构造函数中避免直接操作裸资源,优先使用标准库提供的智能指针,自定义资源(如文件、锁)需封装为独立 RAII 类,杜绝构造过程中的资源泄露。

关键点回顾:

  • 构造函数抛异常 → 析构函数不执行 → 裸资源泄露;
  • RAII:资源绑定到对象生命周期,构造获取、析构释放;
  • 智能指针是 RAII 的 “现成方案”,自定义 RAII 类适配非内存资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1179002.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的路面坑洞检测系统(Python+PySide6界面+训练代码)

摘要路面坑洞是道路基础设施的常见缺陷&#xff0c;对交通安全和车辆维护构成严重威胁。本文详细介绍了一个基于YOLO&#xff08;You Only Look Once&#xff09;系列深度学习模型的路面坑洞检测系统的完整实现方案。系统采用YOLOv5、YOLOv6、YOLOv7和YOLOv8等多种先进目标检测…

金仓数据库如何以“多模融合”重塑文档数据库新范式

文章目录前言性能实测&#xff1a;对标 MongoDB 7.0BSON 引擎对比 Oracle JSON多模融合的关键&#xff1a;不是“堆系统”&#xff0c;而是“一套内核”迁移体验&#xff1a;协议级兼容&#xff0c;替换成本更低高可用与统一运维&#xff1a;关键业务更看重确定性实践案例&…

2026 国产时序数据库全景盘点:从“单点极致”走向“多模融合”

2026 国产时序数据库全景盘点&#xff1a;从“单点极致”走向“多模融合”进入2026年&#xff0c;在“数字中国”与工业物联网浪潮的强劲推动下&#xff0c;国产时序数据库市场持续繁荣&#xff0c;竞争格局日趋清晰。本文将对当前主流的国产时序数据库进行梳理盘点&#xff0c…

Python+django的计算机教学活动教室预约系统聊天机器人

目录摘要开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 Python与Django框架结合开发的计算机教学活动教室预约系统聊天机器人&#xff0c;旨在通过智能化交互提升教育资源的利用率与管…

完整教程:LeetCode 面试题 16.22. 兰顿蚂蚁

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

第三十三周 学习周报

摘要今日学习聚焦Fluent文件管理&#xff1a;掌握.msh、.cas、.dat核心文件作用&#xff0c;并对比.gz与.h5压缩格式的优缺点&#xff0c;为高效仿真文件存储提供选择依据。AbstractTodays learning focuses on Fluent file management: understanding the roles of core files…

213_尚硅谷_接口介绍和快速入门

213_尚硅谷_接口介绍和快速入门1.usb接口调用案例_实际物理接口 2.usb接口调用案例 3.usb接口调用案例_运行结果 4.手机接口案例分解 5.相机接口案例分解 6.电脑接口案例分解

【车载开发系列】AES-CMAC算法基础

【车载开发系列】AES-CMAC算法基础 【车载开发系列】AES-CMAC算法基础【车载开发系列】AES-CMAC算法基础一. 什么是AES二. AES密钥长度三. AES128算法特点四. AES实施步骤五. 算法应用六. 个人总结一. 什么是AES AES&#xff08;Advanced Encryption Standard&#xff09;是对…

2026国产时序数据库风云录:金仓“融合多模”架构异军突起

> 摘要&#xff1a;进入2026年&#xff0c;在“数字中国”与工业物联网浪潮的强劲推动下&#xff0c;国产时序数据库市场持续繁荣&#xff0c;竞争格局日趋清晰。本文将对当前主流的国产时序数据库进行梳理盘点&#xff0c;并特别聚焦于金仓数据库&#xff08;Kingbase&…

搭建 dnsmasq 服务器

dnsmasq 是一个轻量级的 DNS + DHCP + TFTP 集成服务,主要面向:小型网络 虚拟化环境 实验环境 容器 / K8s / OpenStack / libvirt 本地 DNS 缓存与域名解析它的核心特点是:配置简单、占用资源极低、启动快 dnsmasq …

Python+django的基于人脸识别的学生考勤请假选课软件系统

目录基于人脸识别的学生考勤请假选课系统&#xff08;PythonDjango&#xff09;开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于人脸识别的学生考勤请假选课系统&#xff08;PythonDjang…

【车载开发系列】安全算法与安全访问

【车载开发系列】安全算法与安全访问 【车载开发系列】安全算法与安全访问【车载开发系列】安全算法与安全访问一. 网络传输编码1&#xff09;Base64编码2&#xff09;十六进制编码二.四种加密算法1&#xff09;消息摘要算法&#xff08;摘要算法&#xff0c;哈希算法&#xff…

苍穹外卖学习 - day2

写在开头: 佛了,每次想起要写日记的时候,忙来忙去结果忘记写了,算了,先堆一些吧。目录: @目录写在开头:目录:今日完成今日收获12、redis基础今日完成实现了公共字段的自动填充,使用AOP切面的知识,在进行某系…

2025年市面上诚信的多媒体讲台电教桌公司排行,厂区监控杆/防雨套/化验室操作台厂家联系电话 - 品牌推荐师

行业洞察:多媒体讲台电教桌市场的竞争与机遇 随着教育信息化、智能化需求的持续攀升,多媒体讲台电教桌作为教学场景的核心设备,正经历从单一功能向集成化、定制化、智能化的深度转型。市场数据显示,2024年国内多媒…

P_X(x), P(X=1) 的区别;概率度量vs.概率分布

PX(x)P_X(x)PX​(x), P(X1)P(X1)P(X1) 的区别&#xff1b;概率度量vs.概率分布让我们用现实比喻来理解这个“简化计算”的概念。 比喻1&#xff1a;考试成绩统计 原始世界Ω&#xff1a;全班50个学生的完整试卷&#xff08;每道题的具体答案&#xff09; 张三的卷子李四的卷子王…

Python+django的基于学生行为的在线教育 学习选课成绩分析系统可视化统计图没有

目录基于学生行为的在线教育学习选课成绩分析系统可视化统计图摘要开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于学生行为的在线教育学习选课成绩分析系统可视化统计图摘要 该系统采用…

论文卡壳不用愁:AI工具快速生成内容并优化重复率

AI工具性能速览表 工具名称 核心功能 处理时间 AI生成率控制 适配检测平台 askpaper 降AIGC率降重同步 20分钟 个位数 知网/格子达/维普 秒篇 AI痕迹深度弱化 20分钟 个位数 知网/格子达/维普 aicheck 全学科初稿生成 20-30分钟 低水平 - aibiye 文献智能…

评估智能体能力的标准化基准测试

一、引言 随着人工智能技术的飞速迭代&#xff0c;智能体&#xff08;Agent&#xff09;已从单一功能模型演进为具备感知、决策、执行、协作等综合能力的自主系统&#xff0c;广泛渗透到智能客服、自动驾驶、工业质检、科研辅助等多个领域。不同技术路线、应用场景下的智能体层…

折腾笔记[42]-使用标准数据集测试30b模型编程能力

使用标准数据集测试30b-a3b:q8模型编程能力.摘要 使用标准数据集测试30b-a3b:q8模型编程能力. 简介 HumanEval数据集简介 [https://gitcode.com/gh_mirrors/hu/human-eval]This is an evaluation harness for the Huma…

本科毕业论文流程图制作方法

良功绘图网站 (https://www.lghuitu.com ) 本科毕业论文流程图是学术研究过程的可视化呈现&#xff0c;其核心价值在于将复杂的论文撰写流程拆解为清晰、有序的节点&#xff0c;帮助学生梳理研究逻辑、规避流程漏洞&#xff0c;同时也便于指导教师快速把握研究框架、提供针对性…