EffictiveC++笔记 第3章
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的“可能比较准确”的「翻译」。
Chapter 3 资源管理
条款13: 以对象管理资源
有时即使你顺利地写了对应对象的delete语句,但是前面的区域可能会有一个过早的return语句或者抛出了异常.它们一旦执行,控制流绝不会触及delete语句,造成内存泄漏
事实上我们可以将需要的动态资源放进对象内,因为对象的析构函数会自动释放那些资源
c++对应的解决方案有 auto_ptr,也就是所谓的智能指针,它其实是类指针(pointer-like)对象,其析构函数自动对其所指对象调用delete,可一定程度地避免潜在的资源泄漏可能性
先假设一个名叫func的函数,它会new一个xx类型的对象并返回其指针,你可以这样使用auto_ptr :
std::auto_ptr<xx> pt(func());
这个简单例子示范两个关键想法:
---获得资源后立即放进管理对象; 管理对象运用析构函数确保资源被释放---
由于auto_ptr被销毁会自动删除所指之物,所以注意别让多个auto_ptr同时指向一个对象。后果是对象很可能会被删除一次以上,那将使你的程序搭上驶向“未定义行为”的列车上。
为预防这个问题,auto_ptr有一个性质: 若通过copying函数复制它们,它们会变成null,而复制所得指针将取得资源唯一拥有权:
std::auto_ptr<xx> pt1(func());
std::auto_ptr<xx> pt2(pt1); //现在pt2指向原本pt1所指对象,而pt1变成
//nullptr
pt1 = pt2; //现在pt1指向原本pt2所指对象,而pt2变成
//nullptr
可以看出auto_ptr的底层条件:受管理的资源必须绝对没有一个以上的auto_ptrs指向它。对于STL容器,这个特性不是很好。
替代方案是“引用计数型智能指针(reference-counting smart pointer)”,
shared_ptr
它其实也是一个智能指针,持续追踪共有多少对象指向某笔资源,::并在无人指向它时自动删除该资源。::
下面我们会将“引用计数型智能指针(reference-counting smart pointer)”的缩写RCSP来作为shared_ptr的别称。
对于RCSP,你可以这么写:std::shared_ptr<xx> pInv(func());
对于以下操作,RCSP相对于auto_ptr正常多了:
...
std::tr1::shared_ptr<xx> pInv1(func());
std::tr1::shared_ptr<xx>. pInv2(pInv1); //现在pInv2与1指向同一个对象
pInv1 = pInv2; //同上,无任何改变
...
//pInv1和pInv2被销毁后,它们所指的对象也就被自动销毁
综上,RCSP很适合STL容器的操作。
值得注意的是,auto和shared_ptr两者都在其析构函数内做delete而不是delete[]动作。这意味着在动态分配而得的array身上使用auto_ptr和tr1::shared_ptr是馊主意,然而这样会通过编译!
std::auto_ptr<std::string> aps(new std::string[10]);
std::tr1::shared_ptr<int> spi(new int[1024]);
条款14: 在资源管理类中小心copying行为
有可能你偶尔会发现,你需要建立自己的资源管理类
假设使用C API函数处理类型为Mutex的互斥器对象,共有lock和unlock两函数可用:
void lock(Mutek* pm); void unlock(Mutek* pm);
可能需要建立一个class来管理机锁:
class Lock{
public:
explicit Lock(Mutex* pm)
:mutexPtr(pm)
{ lock(mutexPtr); } //获得资源
~Lock() { unlock(mutexPtr); } //释放资源
private:
Mutex* mutexPtr;
};
现在导入一个观念:
资源取得时机便是初始化时机(Resource Acquisition Is Initialization; RAII)
并以此作为“资源管理类的脊柱”
「客户」对Lock的用法符合RAII方式:
Mutex m; //定义需要的互斥器
...
{
Lock ml(&m); //锁定互斥器
...
//在区块末尾解除锁定
}
此时假设Lock对象被复制:
Lock ml1(&m); 锁定m
Lock ml2(ml1); 将ml1复制到ml2身上,会发生啥?
大多数情况你会选择下面两种操作:
- 禁止复制. 许多时候允许RAII对象被复制并不合理,如果你发现不合理,就应该禁止之。
根据条款6,我们发现可以:将copying操作声明为private.对Lock而言看起来是这样:
class Lock: private Uncopyable{
public:
…
};
- 对底层资源祭出“引用计数法”. 有时我们希望保有资源,直到它的最后一个使用者(某对象)被销毁。此时复制RAII对象时应将该资源的“被引用数”递增。也就是使用tr1::shared_ptr
通常只需内含一个tr1::shared_ptr成员变量,RAII classes便可实现出reference-counting copying行为.
存在一个问题:shared_ptr缺省行为是“当引用次数为0时删除所指物”,然而我们想要的动作是解除锁定而非剔除。
幸运的是tr1::shared_ptr允许指定所谓的“删除器(deleter)”——当引用次数为0时便被调用,它是一个函数或函数对象,对于指针是可有可无的第二参数:
class Lock{
public:
explicit Lock(Mutex* pm)
:mutexPtr(pm,unlock)
{
lock(mutexPtr.get()); //以后谈到“get”
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
};
注意,不再声明析构函数,因为没必要,编译器有缺省行为
条款15: 在资源管理器中提供对原始资源的访问
假设有这种情况:
使用RCSP智能指针保存factory函数的调用结果:
std::tr1::shared_ptr<Investment> pInv(createInvestment());
假设有一个函数处理investment对象:
int dayHeld(const Investment* pi); //返回投资天数
而你想这么调用:
int days = daysHeld(pInv);
这样通不过编译,因为即使pInv指向的是investment对象,但是pInv本身是类型为tr1::shared_ptr的对象
这时你需要一个函数来将RAII class对象(本例为tr1::shared_ptr) 转换为其内含原始资源(本例为Investment*)。
有两种办法:显式转换和隐式转换。
tr1::shared_ptr和auto_ptr都提供一个叫get的成员函数,用来执行显式转换,它将返回智能指针内部的原始指针的复件 :
int days = daysHeld(pink.get())
这两种指针还重载了指针取值操作符(operator->和operator*),这将允许隐式转换至原始指针:
class Investment{
public:
bool isTaxFree() const;
};
...
std::tr1::shared_ptr<Investment> pInv(createInvestment());
bool ok = pInv->isTaxFree();
bool ok1 = *(pInv).isTaxFree();
...
很多传统的做法便是xx.get()来获取内部资源,如果这种操作很繁琐的话,有一种更轻松的做法(隐式转换):
class XXX{
public:
...
operator xxx() const //隐式转换函数,xxx是返回类型
{ return f; }
...
};
比如某个函数参数原来得这么写
func(xx.get())
现在你可以写成func(xx)
但这种转换可能会引发错误:
假设类A里储存的是B类对象,当B类对象想拷贝一个A类对象时,却发生了隐式转换:
A obj(getA());
...
B obj2 = obj; //喔唷!原意是拷贝一个A对象,却将obj隐式转换为其底部的B对象了,然后再复制
很多时候显式转换更受欢迎
条款16: 成对使用new和delete时要采取相同形式
看一下这段代码:
std::string* stringArray = new std::string[100];
…
delete stringArray;
很明显此程序行为不明确。对象数组所含的100个string对象中的99
个不太可能被适当删除,因为它们的析构函数很可能没被调用。
delete最大的问题在于:::即将被删除的内存之内究竟有多少对象?::这个问题决定了有多少对象的析构函数必须被调用起来。
也就是, 即将被删除的指针,所指的是单一对象或对象数组? 数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。
所以记住,删除对象数组要用 delete[] xxx;
这种规则对typedef的使用也很重要:
typedef std::string AddressLines[4];
std::string* pal = new AddressLines;
此时AddressLines是一个数组,如果这样new:
std::string* pal = new AddressLines;
就必须匹配 delete[] pal
尽量不要对数组做typedef动作,你大可用STL中的vector等templates
条款17: 以独立语句将newed对象置入智能指针
假设有这样一个函数:
void process(std::tr1::shared_ptr<Widget> pw);
process将对传来的Widget对象运用智能指针
现在考虑调用函数:
process(new Widget);
嘿嘿,这是不能通过编译的。Widget对象需要被shared_ptr包装一下。因为tr1::shared_ptr构造函数需要一个原始指针(raw pointer),但该构造函数是explicit的,无法隐式转换,可以这么写:
process(std::tr1::shared_ptr<Widget>(new Widget));//实际上仍有风险
假设process函数有第二个参数func(),它是一个函数的返回结果:
process(std::tr1::shared_ptr<Widget>(new Widget),func());
调用process之前,编译器必须创建代码,做以下三件事:
- 调用func()
- 执行”new Widget”
- 调用tr1::shared_ptr构造函数
对于c++,此行编译时,有可能编译器先执行”new Widget”操作:
- 执行”new Widget”
- 调用func()
- 调用tr1::shared_ptr构造函数
然而如果此时func()调用导致异常,”new Widget”返回的指针将会遗失,因为它尚未被置入tr1::shared_ptr内,这将引发资源泄漏。
我们可以使用分离语句,将异常干扰减小:
std::tr1::shared_ptr<Widget> pw(new Widget);
process(pw,func());
OVER
EffictiveC++笔记 第3章的更多相关文章
- EffictiveC++笔记 第2章
Chapter 2 构造 / 析构 / 赋值 条款 05:了解C++ 默默编写并调用哪些函数 如果你写下: class Empty{ }; 事实上编译器会帮你补全: class Empty{ publ ...
- EffictiveC++笔记 第1章
Chapter 一: 条款 1 :视 C++为一个语言联邦 (P41 ) c++其实可以视为有四个部分: C Object-Oriented C++ Template C++ STL 条款 2:尽量以 ...
- Stealth视频教程学习笔记(第二章)
Stealth视频教程学习笔记(第二章) 本文是对Unity官方视频教程Stealth的学习笔记.在此之前,本人整理了Stealth视频的英文字幕,并放到了优酷上.本文将分别对各个视频进行学习总结,提 ...
- Stealth视频教程学习笔记(第一章)
Stealth视频教程学习笔记(第一章) 本文是对Unity官方视频教程Stealth的学习笔记.在此之前,本人整理了Stealth视频的英文字幕,并放到了优酷上.本文将分别对各个视频进行学习总结,提 ...
- 20145330《Java学习笔记》第一章课后练习8知识总结以及IDEA初次尝试
20145330<Java学习笔记>第一章课后练习8知识总结以及IDEA初次尝试 题目: 如果C:\workspace\Hello\src中有Main.java如下: package cc ...
- java JDK8 学习笔记——第16章 整合数据库
第十六章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作,称为JDBC驱动程 ...
- CSS3秘笈第三版涵盖HTML5学习笔记1~5章
第一部分----CSS基础知识 第1章,CSS需要的HTML HTML越简单,对搜索引擎越友好 div是块级元素,span是行内元素 <section>标签包含一组相关的内容,就像一本书中 ...
- Android群英传笔记——第七章:Android动画机制和使用技巧
Android群英传笔记--第七章:Android动画机制和使用技巧 想来,最 近忙的不可开交,都把看书给冷落了,还有好几本没有看完呢,速度得加快了 今天看了第七章,Android动画效果一直是人家中 ...
- Android群英传笔记——第六章:Android绘图机制与处理技巧
Android群英传笔记--第六章:Android绘图机制与处理技巧 一直在情调,时间都是可以自己调节的,不然世界上哪有这么多牛X的人 今天就开始读第六章了,算日子也刚好一个月了,一个月就读一半,这效 ...
随机推荐
- linux中安装程序及账户管理
程序安装及管理 1. Linux 应用程序基础 Linux命令与应用程序的关系 1):文件位置 系统命令:一般在/bin和/sbin目录中,或为Shell内部指令 应用程序:通常在/usr/bin和 ...
- Python字符串全解
1.字符串大小写转换 def strChange(): str = "niuXinLong@163.com" print("原字符串:" + str) prin ...
- CAN数据格式-ASC
Vector工具录制的数据,一般有ASC和BLF两种格式,本文介绍ASC. 1. ASC定义 ASC(ASCII)即文本文件,数据已可视化的文本存储. 2.ASC查看 通常情况下,用记事本就可以打开. ...
- JeeSite中Excel导入导出
在各种管理系统中,数据的导入导出是经常用到的功能,通常导入导出以Excel.CSV格式居多.如果是学习的过程中,最好是自己实现数据导入与导出的功能,然而在项目中,还是调用现成的功能比较好.近期一直使用 ...
- Java Code Style
近期困惑于团队成员代码风格迥异,代码质量不可控,作为一名老司机,忧患于后期服务的可维护性,多次一对一的代码Review,耗时耗力不说,效果也不明显.痛定思痛,多次反思之后得出结论:无规矩不成方圆,可靠 ...
- python爬虫入门(四)利用多线程爬虫
多线程爬虫 先回顾前面学过的一些知识 1.一个cpu一次只能执行一个任务,多个cpu同时可以执行多个任务2.一个cpu一次只能执行一个进程,其它进程处于非运行状态3.进程里包含的执行单元叫线程,一个进 ...
- Linux环境安装配置JDK
本文安装环境为Ubuntu14 64位,jdk版本为jdk1.6.0_38,安装文件名为jdk-6u38-linux-x64.bin(根据系统不同,下载不同的安装文件) 下载地址:http://www ...
- Hibernate Session总结
现在我们可以在 IDEA 下新建一个 Hibernate 项目,接着上次内容这次主要总结一下 Hibernate 的 Session,及其核心方法. Session 概述 Session 接口是 Hi ...
- 进阶-MongoDB 知识梳理
MongoDB 一.MongoDB简介 MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql数据库中比较热门的一种.它在许多场景下可用于替代传统的关系型数据库或键/值存储方式.Mo ...
- expdb和impdb使用方法
一 关于expdp和impdp 使用EXPDP和IMPDP时应该注意的事项:EXP和IMP是客户端工具程序,它们既可以在客户端使用,也可以在服务端使用. EXPDP和IMPDP是服务端的工具 ...