章节回顾:
《Effective C++》第1章 让自己习惯C++-读书笔记
《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记
《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记
《Effective C++》第3章 资源管理(1)-读书笔记
《Effective C++》第3章 资源管理(2)-读书笔记
《Effective C++》第4章 设计与声明(1)-读书笔记
《Effective C++》第4章 设计与声明(2)-读书笔记
《Effective C++》第8章 定制new和delete-读书笔记
条款15:在资源管理类中提供对原始资源的访问
许多API直接指涉资源,所以除非你永远不用它们,否则都会绕过资源管理对象直接访问原始资源。假设使用tr1::shared_ptr管理对象。
std::tr1::shared_ptr<Investment> pInv(createInvestment());
函数daysHeld的声明是这样的:
int daysHeld(const Investment *pi);
下面这种调用方式,肯定是错误的:
int days = daysHeld(pInv); //错误
因为函数需要的是指针,你传递是一个tr1::shared_ptr<Investment>对象。所以你需要一个函数将RAII对象转换为所内含的原始资源。有两种方法:隐式转换和显示转换。
(1)显示转换
tr1::shared_ptr和auto_ptr都提供了一个成员函数get返回内部的原始指针,这是显式转换。
int days = daysHeld(pInv.get()); //好的,没有问题
(2)隐式转换
tr1::shared_ptr和auto_ptr都重载了操作符operator->和operator*,这样就允许隐式转换到原始指针。举例:假设Investment类有个成员函数bool isTaxFree() const;那么下面的调用是OK的:
bool taxable1 = !(pInv->isTaxFree()); //好的,没有问题
bool taxable2 = !((*pInv).isTaxFree()); //好的,没有问题
现在的问题是,需要原始指针的地方(例如,函数形参),如何以智能指针代替。解决方法是:提供一个隐式转换函数。下面举个字体类的例子:
FontHandle getFont(); //取得字体句柄
void releaseFont(FontHandle fh); //释放句柄
class Font
{
public:
explicit Font(FontHandle fh) : f(fh){}
~Font()
{
releaseFont(f);
}
private:
FontHandle f;
};
如果C API处理的是FontHandle而不是Font对象,当然你可以像tr1::shared_ptr和auto_ptr那样提供一个get()函数:
FontHandle get() const { return f; } //显示转换函数
这样是可以的,但客户还是觉得麻烦,这时候定义一个隐式转换函数是必须的。
class Font
{
public:
...
operator FontHandle() const { return f; }
...
};
注意:假设你已经知道了隐式转换函数的用法。例如:必须定义为成员函数,不允许转换为数组和函数类型等。
完成了以上工作,对于下面这个函数的调用是OK的:
void changeFontSize(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
changeFontSize(f, newFontSize); //好的,Font隐式转换为FontHandle了。
隐式类型转换也增加了一种风险。例如有以下代码:
Font f1(getFont());
FontHandle f2 = f1; //将Font错写成FontHandle了,编译仍然通过。
f1被隐式转换为FontHandle,这时f1和f2共同管理某个资源,f1被销毁,字体释放,这时候你可以想象f2的状态(原谅我这个词我不会说),再销毁f2,必然会造成运行错误。通常提供一个显示转换get函数是比较好的,因为它可以避免非故意的类型转换的错误,这种错误估计会耗费你很长的调试时间(我遇到过的情况)。
请记住:
(1)有些API要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理的资源”的办法。
(2)对原始资源的访问可以通过显式转换或隐式转换。一般显式转换比较安全,但隐式转换对客户比较方便。
条款16:成对使用new和delete时要采取相同形式
我相信你肯定一眼看出以下代码的问题:
std::string *stringArray = new std::string[];
delete stringArray;
程序行为是未定义的。stringArray所含的100个string对象,99个可能没被删除,因为它们的析构函数没被调用。
delete必须要知道的是:删除的内存有多少个对象,决定了调用多少个析构函数。
单一对象和数组的内存布局肯定是不同的,数组占用的内存也许包含一个“数组大小”的记录。(编译器干的事)你可以告诉编译器删除的是数组还是单一对象:
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[];
delete stringPtr1; //删除一个对象
delete [] stringPtr2; //删除一个数组
这个规则很简单,但有一点需要注意:
typedef std::string AddressLines[];
std::string *pal = new AddressLines; delete pal; //不好,行为未定义。
delete [] pal; //很好。
所以,最好不要对数组做typedef。
请记住:如果new表达式中使用了[],必须在相应的delete表达式中使用[];如果new表达式中不使用[],一定不要在相应的delete表达式中使用[]。
条款17:以独立语句将newed对象置入智能指针
假设有以下函数,具体含义我们可以先忽略:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
先说明一点的是,当你以下面形式调用processWidget时,肯定是错误的:
processWidget(new Widget, priority());
编译出错。tr1::shared_ptr构造函数需要一个原始指针,但该构造函数是explicit,即禁止隐式类型转换的。所以,正常情况你应该这样调用:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()); //好的,没有问题
以这种调用方式,执行函数体之前,有三个工作要做:
(1)调用priority。
(2)执行"new Widget"。
(3)调用tr1::shared_ptr构造函数
可以肯定的是(2)在(3)前面执行,但(1)的执行次序不能确定。假设(1)在第二个被执行,则如果调用priority出现异常,new Widget返回的指针将会遗失,因为还未执行tr1::shared_ptr的构造函数。所以,发生了资源泄露。
避免这类问题很简单:使用分离语句。
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority()); //绝不会造成泄露
请记住:以独立语句将newd对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能造成难以察觉的资源泄露。
《Effective C++》第3章 资源管理(2)-读书笔记的更多相关文章
-
《TCP/IP详解卷1:协议》第2章 链路层-读书笔记
章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...
-
《C++primer》v5 第8章 IO库 读书笔记 习题答案
8.1.8.2 这一章不咋会啊.. istream &read(istream &is) { int a; auto old_state=is.rdstate(); is.clear( ...
-
Java核心技术卷一基础知识-第12章-泛型程序设计-读书笔记
第12章 泛型程序设计 本章内容: * 为什么要使用泛型程序设计 * 定义简单泛型类 * 泛型方法 * 类型变量的限定 * 泛型代码和虚拟机 * 约束与局限性 * 泛型类型的继承规则 * 通配符类型 ...
-
Java核心技术卷一基础知识-第7章-图形程序设计-读书笔记
第7章 图形程序设计 本章内容: * Swing概述 * 创建框架 * 框架定位 * 在组件中显示信息 * 处理2D图形 * 使用颜色 * 文本使用特殊字体 * 显示图像 本章主要讲述如何编写定义屏幕 ...
-
Java多线程编程核心技术-第7章-拾遗增补-读书笔记
第 7 章 拾遗增补 本章主要内容 线程组的使用. 如何切换线程状态. SimpleDataFormat 类与多线程的解决办法. 如何处理线程的异常. 7.1 线程的状态 线程对象在不同的运行时期有不 ...
-
<; 利用Python进行数据分析 - 第2版 >; 第五章 pandas入门 读书笔记
<利用Python进行数据分析·第2版>第五章 pandas入门--基础对象.操作.规则 python引用.浅拷贝.深拷贝 / 视图.副本 视图=引用 副本=浅拷贝/深拷贝 浅拷贝/深拷贝 ...
-
Javascript模式(第四章函数)------读书笔记
一 背景 js函数的两个特点:1 函数是第一类对象(first-class object):2 函数可以提供作用域 1 函数是对象: 1 函数可以在运行时动态创建,还可以在程序执行过程中创建 2 可以 ...
-
Javascript模式(第一章简介)------读书笔记
一:模式 模式是一个通用问题的解决方案,可以提供一个更好的实践经验.有用的抽象化表示和解决一类问题的模板. 本书主要讨论如下三种类型的模式 1 设计模式:可复用面向对象软件的基础,包括singleto ...
-
【我所理解的Cocos2d-x】第六章 精灵Sprite 读书笔记
简介: 精灵是2D游戏里最重要的元素.游戏场景中大部分可见的元素都直接或间接地与精灵相关. 在Cococs2d-xz中,精灵使用Sprite表示,它将一张纹理的一部分或者全部的矩形区域绘制在屏幕上. ...
-
《Java并发编程实战》第六章 任务运行 读书笔记
一. 在线程中运行任务 无限制创建线程的不足 .线程生命周期的开销很高 .资源消耗 .稳定性 二.Executor框架 Executor基于生产者-消费者模式.提交任务的操作相当于生产者.运行任务的线 ...
随机推荐
-
ubuntu终端窗口最大化(不是全屏)
窗口最大化:ctrl+win窗+↑ 窗口还原:ctrl+win窗+↓ 这快捷键让人无语.好好的gnome被改造成unity,快捷键也改掉了.win窗+↑/↓为啥不用呢? 还有就是terminal的ta ...
-
don&#39;t forget the bigger picture
Imagine a circle that contains all of human knowledge: By the time you finish elementary school, you ...
-
初步体验javascript try catch机制
javascript在ECMAScript3中引入了try catch finally机制,大致原理和其他语言一样. 我们也可以自定义错误事件. 但是事先声明:我们自定义的错误事件,只支持对name. ...
-
ubuntu12.04 安装 chrome
1.下载deb包 2. sudo apt-get remove google-chrome-stable sudo dpkg -i google-chrome-stable_current_amd64 ...
-
maven第四章背景案例
4.3简要设计 4.3.1接口设计 4.3.2模块结构 思想 先定义出核心接口,一个接口可以认为是一个功能,根据接口划分功能 设计模式就是一种思想,外观模式和代理模式,适配者模式三者的区别 http: ...
-
【SF】开源的.NET CORE 基础管理系统 - 安装篇
[SF]开源的.NET CORE 基础管理系统 -系列导航 1.开发必备工具 IDE:VS2017 运行环境:netcoreapp1.1 数据库:SQL Server 2012+ 2.获取最新源代码 ...
-
linux中的两个命令setfacl和chmod有什么区别
setfacl命令可以用来细分linux下的文件权限.chmod命令可以把文件权限分为u,g,o三个组,而setfacl可以对每一个文件或目录设置更精确的文件权限. 比较常用的用法如下:setfacl ...
-
C#设计模式(3)——工厂方法模式(Factory Method)
在简单工厂模式中通过工厂Factory获取不同的对象,但是有一个明显的缺点——简单工厂模式系统难以扩展! 一旦添加新产品就不得不修改简单工厂方法,这样就会造成简单工厂的实现逻辑过于复杂, 可以通过工厂 ...
-
HDU 5069 Harry And Biological Teacher(AC自动机+线段树)
题意 给定 \(n\) 个字符串,\(m\) 个询问,每次询问 \(a\) 字符串的后缀和 \(b\) 字符串的前缀最多能匹配多长. \(1\leq n,m \leq 10^5\) 思路 多串匹配,考 ...
-
Freemarker入门
Freemarker入门 工程引入依赖 <dependency> <groupId>org.freemarker</groupId> <artifactId& ...