条款13: 以对象管理资源

2023-11-07

结论

  • 为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
  • 两个常被使用的RAII classes分别是tr1::share_ptr和auto_ptr,前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使他们指向null。

先通过一段小白的代码案例引入主题。假设我们有一个用来表示投资行为的程序库,其中各式各样的投资类型继承自一个root class Investment:

class Investment {...};		// “投资类型”继承体系中的root class

进一步假设,这个程序库通过一个工厂函数(factory function)供应我们某特定的Investment对象:

Investment* createInvestment();		// 返回指针,指向Investment继承体系内的动态分配对象。
									// 调用者有责任删除它。这里为了简化,刻意不写参数。

一如以上注释所言,createInvestment的调用端使用了函数返回的对象后,有责任删除之。现在考虑有个f函数履行了这个责任:

void f()
{
	Investment* pInv = createInvestment();		// 调用factory函数
	...
	delete pInv;								// 释放pInv所指对象
}

代码缺陷分析

这看起来似乎妥当,但若干情况下f可能无法删除它得自createInvestment的投资对象——1)或许因为“…”区域内的一个过早的return语句。如果这样一个return被执行起来,控制流就绝不会触及delete语句;2)类似情况发生在对createInvestment的使用及delete动作位于某循环内,而该循环由于某个continue或goto语句过早退出;3)最后一种可能是“…”区域内的语句跑出异常,果真如此控制流将再次不会幸临delete。无论delete如何被略过去,我们泄漏的不只是内含投资对象的那块内存,还包括那些投资对象所保存的任何资源

代码的可维护性也是需要考虑的一个方面。谨慎地编写程序可以防止这一类错误。但是你必须想,代码可能会在时间渐渐过去后被修改。一旦软件开始接受维护,可能会有某些人添加return语句或者continue语句而未能全然领悟它对函数的资源管理策略造成的后果。更糟糕的是f的“…”区域有可能调用一个“过去从不抛出异常,却在被“改善”之后开始那么做”的函数。因此单纯依赖“f总是会执行delete语句”是行不通的。

解决方案:RAII(Resource Acquisition Is Initialization)

为了确保createInvestment返回的资源总是被释放,我们需要将资源放进对象内,我们便可以依赖C++的“析构函数自动调用机制”确保资源被释放。

方案一:auto_ptr

auto_ptr是个“类指针对象”,也就是所谓“智能指针”,其析构函数自动对其所指对象调用delete。许多资源被动态分配于heap内而后被用于单一区块或函数内,它们应该是在控制流离开那个区块或函数时被释放。下面示范如何使用auto_ptr以避免f函数潜在的资源泄漏的可能性:

void f()
{
	std::auto_ptr<Investment> pInv(createInvestment());	// 调用factory函数
														// 一如即往地使用pInv
	...													// 经由auto_ptr的析构函数自动删除pInv
}

这个简单的例子示范**“以对象管理资源”的两个关键想法**:

  • 获得资源后立刻放进管理对象内。以上代码中createInvestment返回的资源被当作其管理者auto_ptr的初值。实际上“以对象管理资源”的观念常被称为“资源取得时机便是初始化时机”(Resource Acquisition is Initialization;RAII),因为我们几乎总是在获得一笔资源后同一语句内以它初始化某个管理对象。有时候活的的资源被拿来赋值(而非初始化)某个管理对象,但不论哪一种做法,每一笔资源都在获得的同时立刻被放进管理对象中。
  • 管理对象运用析构函数确保资源被释放。不论控制流如何离开区块,一旦对象被销毁(例如当对象离开作用域)其析构函数自然会被自动调用,于是资源被释放了。如果资源释放动作可能导致抛出异常,事情变得有点棘手,但是条款8能够解决该问题,在这里不多赘述。

auto_ptr使用的注意事项
由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一对象。如果真实那样,对象会被删除一次以上,而那会使你的程序搭上驶向“未定义行为”的快速列车上。为了预防这个问题,auto_ptr有一个不同寻常的性质:若通过copy构造函数或者copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一所有权

std::auto_ptr<Investment> pInv1(createInvestment());		// pInv1指向createInvestment返回物
std::auto_ptr<Investment> pInv2(pInv1);						// 现在PInv2指向对象,pInv1被设为null
pInv1 = pInv2;												// 现在pInv1指向对象,pInv2被设为null

方案二:shared_ptr

auto_ptr的替代方案是“引用计数型智慧指针”(reference-counting smart pointer,RCSP)。所谓RCSP也是个智能指针,持续跟踪共有多少个对象指向某笔资源,并在无人指向它时自动删除该资源。RCSP提供的行为类似垃圾回收,不同的是RCSP无法打破环状引用(例如两个其实已经没被使用的对象互指,因而好像还处在“被使用”状态)。

TR1的tr1::shared_ptr就是个RCSP,所以你可以这么写:

void f()
{
	...
	std::TR1::shared_ptr<Investment> pInv(createInvestment());	// 调用factory函数
	...															// 经由shared_ptr析构函数自动删除ptr
}

这段代码看起来几乎和使用auto_ptr的那个版本相同,但shared_ptr的复制行为正常多了:

void f()
{
	std::shared_ptr<Investment> pInv1(createInvestment());		// pInv1指向createInvestment返回物
	std::shared_ptr<Investment> pInv2(pInv1);					// pInv1和PInv2指向同一个对象
	pInv1 = pInv2;												// 同上,无任何改变
	...
}												// pInv1和pInv2被销毁,它们所指的对象也就被自动销毁

由于tr1::shared_ptr的复制行为“一如预期”,它们可以被用于STL容器,以及其他“auto_ptr之非正统复制行为并不适用”的语境上。

注意
auto_ptr和shared_ptr两者都在其析构函数内做delete,而不是delete[]动作。那意味在动态分配而得的array上使用auto_ptr或shared_ptr是个馊主意,尽管如此,但是能编译通过。

std::auto_ptr<std::string> aps(new std::string[10]);	// 馊主意,会用上错误的delete
std::shared_ptr<int> sp1(new int[1024]);				// 相同问题

你会惊讶地发现,并没有特别针对“C++动态分配数组”而设计的类似的auto_ptr或shared_ptr那样的东西,甚至TR1中也没有。那是因为vector和string几乎总是可以取代动态分配而得的数组。如果你还是认为拥有针对数组而设计的,类似auto_ptr和shared_ptr那样的class较好,看看Boost吧。在那你会很高兴地发现boost::scoped_array和boost::shared_array ,它们都能提供你要的行为。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

条款13: 以对象管理资源 的相关文章

  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • 未解决的包含:“cocos2d.h” - Cocos2dx

    当我在 Eclipse 中导入 cocos2dx android 项目时 我的头文件上收到此警告 Unresolved inclusion cocos2d h 为什么是这样 它实际上困扰着我 该项目可以正确编译并运行 但我希望这种情况消失
  • C++ 子字符串返回错误结果

    我有这个字符串 std string date 20121020 我正在做 std cout lt lt Date lt lt date lt lt n std cout lt lt Year lt lt date substr 0 4 l
  • 当 contains() 工作正常时,xpath 函数ends-with() 工作时出现问题

    我正在尝试获取具有以特定 id 结尾的属性的标签 like span 我想获取 id 以 国家 地区 结尾的跨度我尝试以下xpath span ends with id Country 但我得到以下异常 需要命名空间管理器或 XsltCon
  • 指针问题(仅在发布版本中)

    不确定如何描述这一点 但我在这里 由于某种原因 当尝试创建我的游戏的发布版本进行测试时 它的敌人创建方面不起作用 Enemies e level1 3 e level1 0 Enemies sdlLib 500 2 3 128 250 32
  • 在 Visual Studio 2008 上设置预调试事件

    我想在 Visual Studio 中开始调试程序之前运行一个任务 我每次调试程序时都需要运行此任务 因此构建后事件还不够好 我查看了设置的 调试 选项卡 但没有这样的选项 有什么办法可以做到这一点吗 你唯一可以尝试的 IMO 就是尝试Co
  • Json.NET - 反序列化接口属性引发错误“类型是接口或抽象类,无法实例化”

    我有一个类 其属性是接口 public class Foo public int Number get set public ISomething Thing get set 尝试反序列化Foo使用 Json NET 的类给我一条错误消息
  • WPF TabControl,用C#代码更改TabItem的背景颜色

    嗨 我认为这是一个初学者的问题 我搜索了所有相关问题 但所有这些都由 xaml 回答 但是 我需要的是后台代码 我有一个 TabControl 我需要设置其项目的背景颜色 我需要在选择 取消选择和悬停时为项目设置不同的颜色 非常感谢你的帮助
  • 在 ASP.NET Core 3.1 中使用包含“System.Web.HttpContext”的旧项目

    我们有一些用 Net Framework编写的遗留项目 应该由由ASP NET Core3 1编写的API项目使用 问题是这些遗留项目正在使用 System Web HttpContext 您知道它不再存在于 net core 中 现在我们
  • 如何将图像路径保存到Live Tile的WP8本地文件夹

    我正在更新我的 Windows Phone 应用程序以使用新的 WP8 文件存储 API 本地文件夹 而不是 WP7 API 隔离存储文件 旧的工作方法 这是我如何成功地将图像保存到 共享 ShellContent文件夹使用隔离存储文件方法
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • Qt表格小部件,删除行的按钮

    我有一个 QTableWidget 对于所有行 我将一列的 setCellWidget 设置为按钮 我想将此按钮连接到删除该行的函数 我尝试了这段代码 它不起作用 因为如果我只是单击按钮 我不会将当前行设置为按钮的行 ui gt table
  • 从库中捕获主线程 SynchronizationContext 或 Dispatcher

    我有一个 C 库 希望能够将工作发送 发布到 主 ui 线程 如果存在 该库可供以下人员使用 一个winforms应用程序 本机应用程序 带 UI 控制台应用程序 没有 UI 在库中 我想在初始化期间捕获一些东西 Synchronizati
  • 将 unsigned char * (uint8_t *) 转换为 const char *

    我有一个带有 uint8 t 参数的函数 uint8 t ihex decode uint8 t in size t len uint8 t out uint8 t i hn ln for i 0 i lt len i 2 hn in i
  • 将 xml 反序列化为类,list<> 出现问题

    我有以下 XML
  • 如何使我的表单标题栏遵循 Windows 深色主题?

    我已经下载了Windows 10更新包括黑暗主题 文件资源管理器等都是深色主题 但是当我创建自己的 C 表单应用程序时 标题栏是亮白色的 如何使我自己的桌面应用程序遵循我在 Windows 中设置的深色主题 你需要调用DwmSetWindo
  • Process.Start 阻塞

    我正在调用 Process Start 但它会阻止当前线程 pInfo new ProcessStartInfo C Windows notepad exe Start process mProcess new Process mProce
  • mysql-connector-c++ - “get_driver_instance”不是“sql::mysql”的成员

    我是 C 的初学者 我认为学习的唯一方法就是接触一些代码 我正在尝试构建一个连接到 mysql 数据库的程序 我在 Linux 上使用 g 没有想法 我运行 make 这是我的错误 hello cpp 38 error get driver
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我

随机推荐

  • 树莓派教程 - 1.0 树莓派GPIO库wiringPi 点亮LED

    Git例程源码仓库 https github com ZhiliangMa raspberry git 电脑需要提前安装的工具 SSH 串口 终端神器 MobaXterm 官网下载链接 https mobaxterm mobatek net
  • java.sql.sQLException: No suitable driver found for jdbc:mysql

    用Maven构建父工程后 再搭建DAO模块 执行测试类测试StudentDao java查询数据库功能报错 定位到相应的类中 发现是数据库连接问题 之前我也遇到类似的问题 解决方案为 jdbc url jdbc mysql localhos
  • YOLO系列目标检测算法-Scaled-YOLOv4

    YOLO系列目标检测算法目录 文章链接 YOLO系列目标检测算法总结对比 文章链接 YOLOv1 文章链接 YOLOv2 文章链接 YOLOv3 文章链接 YOLOv4 文章链接 Scaled YOLOv4 文章链接 YOLOv5 文章链接
  • 主业工资5000,靠“爬虫技术”月入20000:会赚钱的人,从不靠拼命

    因为我们主行业的一个内卷以及薪资问题 现在已经有很多人都在从事一个副业 副业往往比主业更自由 轻松 甚至有的副业工资比他的一个主业工资还高 现在 只要你会python的爬虫技术 网上接单副业的项目更是多的供过于求 现在业界对Python爬虫
  • Python和OpenCV 提取图像特定目标区域-图像分割【原创】

    在OpenCV中 一般转换图像格式为HSV格式 默认格式为BGR 再进行指定颜色的提取 直接使用RGB提取颜色不推荐 HSV格式的介绍 HSV 为色相 饱和度 明度 1 将一副图像从rgb颜色空间转换到hsv颜色空间 hsv cv2 cvt
  • <Visual Studio 2019安装及环境配置教程>

    目录 1 Visual Studio 2019下载安装 1 1 进入官网 1 2 选择所需Visual Studio 2019安装包 1 3 Visual Studio 2019 版本说明 1 4 点击下载 安装 1 5 登录账户 1 6
  • 2022年华中杯数学建模挑战赛B题量化投资问题求解全过程文档及程序

    2022年华中杯数学建模 B题 量化投资问题 原题再现 量化投资是指通过数量化方式及计算机程序化发出买卖指令 以获取稳定收益为目的的交易方式 投资者通过数据分析探索市场运行规律 并预测市场走势 从而进行决策交易 随着大数据技术的发展 量化投
  • windows11安装wsl

    wsl2 简单点说 就是可以在Windows上用Linux 并且支持docker 管理员身份打开powerShell win x 输入下面命令 wsl install dism exe online enable feature featu
  • 算法(C++)金币阵列问题

    题目描述 有m n m 100 n 100 个金币在桌面上排成一个m 行n 列的金币阵列 每一枚金币或正面朝上或背面朝上 用数字表示金币状态 0 表示金币正面朝上 1 表示背面朝上 金币阵列游戏的规则是 1 每次可将任一行金币翻过来放在原来
  • 从mpeg ts文件中提取I帧(3):pes包的解析

    本系列的第一篇文章讲解了如何把ts包拼装为pes包 本章主要讲解如何解析pes包 一 pes包的格式如下图所示 二 相关字段解析 packet start code prefix 标识包起始端的包起始码 固定值为 0000 0000 000
  • 沟通——职场生存的润滑剂

    我们每天都有和别人沟通互动 但经常不经意地话不投机或语出伤人都不自知 在工作中 沟通也是必不可少的 经常听到企业的领导强调沟通 上下级沟通 同事间沟通 的确 沟通可以大大提升工作效率 沟通是一种技能 是一个人对本身知识 表达能力 行为能力的
  • 嵌入式 Linux 系统在线升级策略-固件升级-升级固件-系统升级

    如果你认为本系列文章对你有所帮助 请大家有钱的捧个钱场 点击此处赞助 赞助额1元起步 多少随意 锋影 email 174176320 qq com 嵌入式 Linux 系统在线升级策略 对于运行 Linux 系统的嵌入式产品 很多时候我们发
  • 微信调用jssdk全流程详解

    微信调用jssdk全流程详解 系统框架使用的是前后端分离 前端使用vant 后端是springboot 一 网页授权的时序图 二 公众号配置 1 绑定域名 登录微信公众平台进入 公众号设置 的 功能设置 里填写 JS接口安全域名 也就是这样
  • MongoDB—索引的建立与维护

    一 索引基础 MongoDB的索引几乎与传统的关系型数据库一模一样 这其中也包括一些基本的优化技巧 下面是创建索引的命令 gt db test ensureIndex username 1 可以通过下面的名称查看索引是否已经成功建立 gt
  • java获取相对路径

    在jsp和class文件中调用的相对路径不同 在jsp里 根目录是WebRoot 在class文件中 根目录是WebRoot WEB INF classes 当然你也可以用System getProperty user dir 获取你工程的
  • sqli-labs-less-12 PODT传参+有回显信息(图文详解)

    Less 12 post传递参数 由于是post传参 我们先用burp suite抓包 分析报文体 获取传参过程 得到报文体之后使用hackbar插件中的post data进行注入实验 判断闭合方式 uname or 1 1 passwd
  • 【一周算法实践集训】_【模型构建】_baseline

    读取数据 import pandas as pd data all pd read csv data all csv encoding gbk encoding gbk 解决编码问题 划分数据集 划分训练集测试集 from sklearn
  • 讨论保护网络隐私的方法与策略

    随着网络技术的不断发展 网络成为了人们日常生活中最为重要的媒介之一 每个人都在网络上留下了大量的个人信息和隐私 这些信息有时甚至比我们现实生活中留下的资料更为丰富和详尽 然而 随着网络安全问题层出不穷 网络上的这些个人信息也暴露在了一些不良
  • Python新手入门-软件安装配置篇

    Python开发建议使用 pycharm Anaconda Anaconda中包含Python解释器及各种要用到的工具包 使用十分方便 如果只是下载Python的话 后期使用过程中还要自行下载安装各种安装包 网速可以的话还好 不好的话半天下
  • 条款13: 以对象管理资源

    结论 为防止资源泄漏 请使用RAII对象 它们在构造函数中获得资源并在析构函数中释放资源 两个常被使用的RAII classes分别是tr1 share ptr和auto ptr 前者通常是较佳选择 因为其copy行为比较直观 若选择aut