Effective C++ 学习笔记 条款13 以对象管理资源

假设我们使用一个用来塑模投资行为(如股票、债券等等)的程序库,其中各式各样的投资类型继承自一个root class Investment:

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

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

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

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

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

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

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

为确保createInvestment返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f,该对象的析构函数会自动释放那些资源。实际上这正是隐身于本条款内容里的部分想法:把资源放进对象内,我们便可倚赖C++的“析构函数自动调用机制”确保资源被释放(稍后讨论另一部分想法)。

许多资源被动态分配于heap内而后被用于单一区块或函数内。它们应该在控制流离开那个区块或函数时被释放。标准程序库提供的auto_ptr正是针对这种形式而设计的特制产品。auto_ptr是个“类指针(pointer-like)对象”,即所谓的“智能指针”,其析构函数自动对其所指对象调用delete。下面示范如何使用auto_ptr以避免f函数潜在的资源泄露可能性:

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

这个简单的例子示范“以对象管理资源”的两个关键想法:
1.获得资源后立刻放进管理对象(managing object)内。以上代码中createInvestment返回的资源被当做其管理者auto_ptr的初值。实际上“以对象管理资源”的观念常被称为“资源取得时机便是初始化时机”(Resource Acquisition Is Initialization,RAII),因为我们几乎总是在获得一笔资源后于同一语句内以它初始化某个管理对象。有时候获得的资源被拿来赋值(而非初始化)某个管理对象,但无论哪一种做法,每一笔资源都在获得的同时立刻被放进管理对象中。

2.管理对象(managing object)运用析构函数确保资源被释放。不论控制流如何离开区块,一旦对象被销毁(例如当对象离开作用域)其析构函数自然会被自动调用,于是资源被释放。如果资源释放动作可能导致抛出异常,事情变得有点棘手,但条款8已经能够解决这个问题,所以这里我们也就不多操心了。

由于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

这一诡异的复制行为,加上其底层条件:“受auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它”,意味着auto_ptr并非管理动态分配资源的神兵利器。举个例子,STL容器要求其元素发挥“正常的”复制行为,因此这些容器容不得auto_ptr。

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

TR1的tr1::shared_ptr(见条款54)就是个RCSP,所以你可以这么写f:

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

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

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

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

尽管如此,可别误会了,本条款并不专门针对auto_ptr、tr1::shared_ptr或其他任何智能指针,而只是强调“以对象管理资源”的重要性,auto_ptr和tr1::shared_ptr只不过是实际例子。如果想知道tr1::shared_ptr的更多信息,请看条款14、18、54。

auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作(条款16对两者的不同有描述)。那意味着在动态分配而得的array身上用auto_ptr或tr1::shared_ptr是个馊主意。尽管如此,可叹的是,那么做仍能通过编译:

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

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

本条款也建议,如果你打算手工释放资源(例如使用delete而非使用一个资源管理类(resource-managing class)),容易发生某些错误。罐装式的资源管理类如auto_ptr和tr1::shared_ptr往往比较能够轻松遵循本条款忠告,但有时你所使用的资源是目前这些预制式class无法妥善管理的。既然如此就需要精巧制作你自己的资源管理类。那并不是非常困难,但的确涉及若干你需要考虑的细节。那些考虑形成了条款14和15的标题。

作为最后批注,必须指出,createInvestment返回的“未加工指针”(raw pointer)简直是对资源泄露的一个死亡邀约,因为调用者极易在这个指针身上忘记调用delete(即使他们使用auto_ptr或tr1::shared_ptr来执行delete,他们首先必须记得将createInvestment的返回值存储于智能指针对象内)。为与此问题搏斗,首先需要对createInvestment进行接口修改,那是条款18面对的事。

请记住:
1.为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。

2.两个常被使用的RAII class分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null。

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

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

相关文章

机器视觉 /从bottle.hdev示例程序开启HalconHDevelop征程

文章目录 概述示例程序bottle.hdev源码Step 0: PreparationsStep 1: Segmentation - 读取并显示图片Step 1: Segmentation - 创建并设置OCR模型Step 1: Segmentation - 文本分割与识别计算结果显示内存释放 导出为C代码导出为C代码配置 VS Halcon 环境VS程序执行结果HTuple hv…

LeetCode刷题---填充每个节点的下一个右侧节点指针

官方题解:LeetCode官方题解 解题思想: 因为是一棵满二叉树&#xff0c;所以除了叶子节点外的其他节点都有两个子节点。 可以根据每一层来依次遍历 从根节点开始&#xff0c;根节点的左子节点的next节点就指向根节点的右子节点 因为根节点的next节点为NULL&#xff0c;开始从根…

centOS7操作系统安装说明

一、安装前准备 在安装CentOS 7之前&#xff0c;确保你已经下载了CentOS 7的ISO镜像文件。你可以从CentOS官网下载&#xff1a;The CentOS Project 1. 安装环境准备 确保你的计算机满足CentOS 7的最低系统要求。CentOS 7支持的最低系统要求如下&#xff1a; x86-64或x86架构…

DR模式下LVS负载均衡聚集部署实验

目录 1、实验准备 2、配置负载调度器&#xff08;ens33&#xff1a;192.168.80.9 VIP:192.168.80.188&#xff09; 2.1 配置虚拟ip地址&#xff08;VIP&#xff1a;192.168.80.188&#xff09; 2.2 调整proc响应参数 2.3 设置负载分配策略 3、部署共享存储&#xff08;NF…

LeetCode1394. Find Lucky Integer in an Array

文章目录 一、题目二、题解 一、题目 Given an array of integers arr, a lucky integer is an integer that has a frequency in the array equal to its value. Return the largest lucky integer in the array. If there is no lucky integer return -1. Example 1: Inp…

【算法可视化】搜索算法专题

运行平台 Algorithm Visualizer 选数 [NOIP2002 普及组] 选数 // 导入可视化库 { const { Tracer, Array1DTracer, LogTracer, Layout, VerticalLayout } require(algorithm-visualizer); // }const N 4, K 3; //从包含4个元素的集合中选出3个数 let ans 0 //方案数 co…

static详解

前言 大家好我是jiantaoyab&#xff0c;这篇文章来谈一谈c中的static&#xff0c;根据对static的使用&#xff0c;我分为类内和类外2种情况 static简介 static是c常用的修饰符&#xff0c;它用来控制变量的存储方式和可见性&#xff0c;在变量前面加上一个static&#xff0c…

代码随想录算法训练营第五十二天 300.最长递增子序列 、674. 最长连续递增序列 、718. 最长重复子数组

代码随想录算法训练营第五十二天 | 300.最长递增子序列 、674. 最长连续递增序列 、718. 最长重复子数组 300.最长递增子序列 题目链接&#xff1a;300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int lengthOfLIS(int[] nums) {int l…

ECMAScript 语法

ECMAScript 语法 一、ECMAScript1.ECMAScript简介2.ECMAScript历史 二、ECMAScript 语法区分大小写变量是弱类型的每行结尾的分号可有可无注释与 Java、C 和 PHP 语言的注释相同括号表示代码块 一、ECMAScript ECMAScript是一种由Ecma国际&#xff08;前身为欧洲计算机制造商协…

大唐杯学习笔记:Day6

1.1小区选择 一、概述 1.UE在RRC_IDLE和RRC——INACTIVATE状态下进行的过程&#xff1b; 2.UE首先需要完成PLMN的选择,在已选择的PLMN上寻找合适的小区,获取合适的服务,监听控制信道,这个过程即小区选择过程&#xff1b; 3.根据小区重选准则,UE寻找其他更适合的小区进行小区…

论文《Exploring CLIP for Assessing the Look and Feel of Images》阅读

论文《Exploring CLIP for Assessing the Look and Feel of Images》阅读 论文概述Preliminary方法论Experiments结论 论文概述 今天带来的是论文《Exploring CLIP for Assessing the Look and Feel of Images》&#xff0c;论文主要通过 CLIP 模型来完成图像的质量&#xff0…

js五星评价的制作方法

方法有两种&#xff0c;1、jquer插件&#xff1b;2、图片循环&#xff1b; 第一种、效果图 代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"…

装饰器模式详解

8.9.6 装饰器模式 ​ 装饰器模式是一种结构型模式&#xff0c;主要是给一个类添加更多功能&#xff1b; 示例代码&#xff1a; #include <iostream> #include <string>// 抽象基类&#xff1a;文本修饰器 class TextDecorator { public:virtual std::string dec…

软件中级设计师——计算机系统知识

前言 计算机系统知识点&#xff08;第一章&#xff09;。 文章目录 前言一、计算机系统1、CPU2、运算器3、控制器 一、计算机系统 1、CPU 主要由控制器和运算器组成&#xff1b; 控制器 功能 程序控制&#xff1b;操作控制&#xff1b;时间控制&#xff1b; 运算器 功能 数据…

自定义过滤器实现对请求报文统一解密对响应加密

工作中经常会遇到这样的情况,前端(Android或vue等)跟后台通讯时需要对报文做加密和签名处理,但是后端微服务之间调用是明文,这种情况可以考虑通过自定义过滤器的方式实现。 前端在请求头里增加特定字段表示是前端请求,报文是否需要加密,后端自定义过滤器获取请求时根据请…

一文了解 ArrayList 的扩容机制

了解 ArrayList 在 Java 中常用集合类之间的关系如下图所示&#xff1a; 从图中可以看出 ArrayList 是实现了 List 接口&#xff0c;并是一个可扩容数组&#xff08;动态数组&#xff09;&#xff0c;它的内部是基于数组实现的。它的源码定义如下&#xff1a; public class A…

通过hyperbeam创建梁单元截面属性

1、为模型中标准的圆柱形创建梁单元和赋予属性&#xff1b; 2、为模型中不标准的对称性实体创建梁单元和赋予属性&#xff1b; 3、为模型中壳体部分创建梁单元和赋予属性&#xff1b;

Linux系统之rename命令的基本使用

Linux系统之rename命令的基本使用 一、rename命令介绍二、raname工具版本2.1 C语言版本2.2 Perl版本 三、centos下的rename使用3.1 基本语法3.2 命令选项3.3 rename的基本使用 四、ubuntu下的rename使用4.1 基本语法4.2 命令选项4.3 rename命令的基本操作 五、rename注意事项 一…

“色狼”用英语怎么说?柯桥日常英语,成人英语口语学习

最近有粉丝问我"色狼"英文翻译是啥 首先声明不是"colour wolf"哈 关于“色狼”的英文表达有很多 快和C姐一起来看看吧&#xff01; 1.pervert 这个单词的意思是变态、色狼 是对性变态者最直观的描述 He is such a pervert&#xff01; I saw him lo…

【Java】Spring的ReflectionUtils类常用方法学习笔记

目录 ReflectionUtils介绍 常用方法 访问字段 方法调用 处理回调 示例 脑容量不够了&#xff0c;以简单的小知识作为一天的结尾吧(悲 ReflectionUtils介绍 ReflectionUtils是Spring Framework中非常实用的一个工具类&#xff0c;为开发人员提供了简便的反射操作方法&am…