Effective C++ —— 资源管理(三)
条款13 : 以对象管理资源
假设有如下代码:
Investment* createInvestment(); //返回指针,指向Investment继承体系内的动态分配对象,调用者有责任删除它 void func()
{
Investment* pInv = createInvestment(); //调用factory函数
.....
delete pInv; //释放pInv所指对象
}
上述代码可能出现如下问题导致无法删除pInv指针所指对象,出现资源泄露。
(1)“.....”区域内一个过早结束的return语句;
(2)delete动作位于某个循环内,而该循环由于某个continue或goto语句过早结束;
(3)“.....”区域内语句抛出异常;
解决方案:把资源放进对象内,我们便可倚赖C++的“析构函数自动调用机制”确保资源被释放。标准程序库提供的auto_ptr正是针对这种形势而设计的特制产品。auto_ptr是个“类指针(pointer-like)对象”,也就是所谓“智能指针”,其析构函数自动对其所指对象调用delete。如下:
void func()
{
std::auto_ptr<Investment> pInv (createInvestment());
..... // 调用factory函数,经由auto_ptr的析构函数自动删除pInv
}
解析:
1. 获得资源后立刻放进管理对象内。实际上,“以对象管理资源”的观念常被称为“资源取得时机便是初始化时机”(Resource Acquisition Is Initialization;RAII)。每一笔资源都在获得的同时立刻被放进管理对象中。
2. 管理对象运用析构函数确保资源被释放。即便析构抛出异常,条款08也已经给出解决方案。
这里简单介绍一下“智能指针”:
auto_ptr采用“所有权”方式管理对象,也即对于auto_ptr的赋值、复制操作将直接交割对象的所有权,所以一定注意不要让多个auto_ptr同时指向同一个对象。
auto_ptr的替代方案是“引用计数型智慧指针”(reference-counting smart pointer;RCSP),其也是一个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。TR1的tr1::shared_ptr(条款54)就是个RCSP。上述代码可修改如下:
void func()
{
.....
std::tr1::shared_ptr<Investment> pInv (createInvestment());
..... // 调用factory函数,经由shared_ptr的析构函数自动删除pInv
}
注:上述auto_ptr和tr1::shared_ptr只不过是“以对象管理资源”在本条款中所使用的例子。同时,createInvestment返回“未加工指针”(raw pointer)简直是对资源泄漏的一个死亡邀约,其一,调用者极易在这个指针身上忘记调用delete;其二,即使想使用智能指针,也有可能会忘记将createInvestment的返回值存储于智能对象内。所以,条款18提供了一个解决方法:令createInvestment返回一个智能指针。如:
std::tr1::shared_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<investment> retVal (static_case<Investment*>(), getRidOfInvestment); // 第一个参数是指针,使用cast转型得到 retVal = ....; //令retVal指向正确对象
return retVal;
}
这便强迫客户将返回值存储于一个tr1::shared_ptr内。
故而:
1. 为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
2. 两个常被使用的RAII classes分别是auto_ptr和tr1::shared_ptr。后者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向Null。
条款14 : 在资源管理类中小心copying行为
条款13导入这样的观念:“资源取得时机便是初始化时机”(RAII),并以此作为“资源管理类”的脊柱,也描述了auto_ptr和tr1::shared_ptr如何将这个观念表现在heap-based(基于堆)资源上。然而,并非所有的资源都是heap-based,对那种资源而言,像auto_ptr和tr1::shared_ptr这样的智能指针往往不适合作为资源掌管者。偶尔,我们需要建立自己的资源管理类。考虑如下代码:
// Metex的互斥器对象,为确保绝不会忘记将一个被锁住的Mutex解锁,需要建立一个class管理机锁
class Lock {
public:
explicit Lock(Mutex* pm)
:mutexPtr(pm)
{ lock(mutexPtr); } // 获得资源
~Lock() { unlock(mutexPtr); } // 释放资源
private:
Mutex *mutexPtr;
}; // 客户对Lock的用法符合RAII方式
Mutex m; //定义你需要的互斥器
.....
{ // 建立一个区块用来定义critical section.
Lock ml(&m); // 锁定互斥器
..... // 执行critical section内的操作
} //如果Lock对象被复制,会发生什么事?
Lock ml1(&m); //锁定m
Lock ml2(ml1); // 将ml1复制到ml2身上,这会发生什么事 ?
面对RAII对象被复制,可选择的解决方案:
1. 禁止复制。条款6已经说明如何禁止复制动作。(将copying函数声明为private)
2. 对底层资源祭出“引用计数法”。tr1::shared_ptr便是如此。可将mutexPtr类型从Mutex* 改为 tr1::shared_ptr<Mutex>.
注意:面对互斥器,当引用计数为0时,我们想要做的释放动作是解除锁定而非删除。幸运的是tr1::shared_ptr允许指定所谓的“删除器”,那是一个函数或函数对象,当引用计数为0时便被调用。如下:
class Lock {
public:
explicit Lock(Mutex* pm) // 以某个Mutex初始化shared_ptr
:mutexPtr(pm, unlock) // 并以unlock函数为删除器
{
lock(mutexPtr.get()); //条款15谈到“get"
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr; //使用shared_ptr替换raw pointer
};
本例的Lock class不再声明析构函数。因为没有必要。条款05说过,class 析构函数会自动调用其non-static成员变量(本例为mutexPtr)的析构函数。而mutexPtr的析构函数会在互斥器的引用计数为0时自动调用tr1::shared_ptr的删除器(本例为unlock).
3. 复制底部资源。也就是说,复制资源管理对象是,进行的是”深度拷贝“。
4. 转移底部资源的拥有权。这是auto_ptr奉行的复制意义。
故而:
1. 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
2. 普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法。不过其他行为也都可能被实现。
条款15 : 在资源管理类中提供对原始资源的访问
许多APIs直接指涉原始资源,所以提供对原始资源的访问有时很必要。
1. tr1::shared_ptr和auto_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件):
2. 就像(几乎)所以智能指针一样,tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator-> 和 operator*),他们允许隐式转换至底部原始指针。
如:
class Investment {
public:
bool isTaxFree() const;
....
};
Investment * createInvestment (); //factory函数
std::tr1::shared_ptr<Investment> pi1(createInvestment()); //令tr1::shared_ptr管理一笔资源
bool taxable1 = !(pi1->isTaxFree()); //经由operator->访问资源,pi1隐式转换至底部原始指针,调用原始指针成员函数
........
3. 对于资源管理类,显式转换和隐式转换例子如下 :
FontHandle getFont();
void releaseFont(FontHandle fh); class Font { //RAII class
public:
explicit Font(FontHandle fh) // 获得资源
:f(fh) //采用pass-by-value,因为C API这样做。
{ }
~Font() { releaseFont(f); }
private:
FontHandle f; //原始(raw)字体资源
}; //显式转换--------------------------------------------------------------
class Font {
public:
......
FontHandle get() const { return f; } //显式转换函数
......
};
// 客户调用
void changeFontSize(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
.....
changeFontSize(f.get(), newFontSize); //显式将Font转换为FontHandle //隐式转换--------------------------------------------------------------
class Font {
public:
.....
operator FontHandle() const // 隐式转换函数
{ return f; }
......
};
//客户调用
Font f(getFont());
int newFontSize;
.....
changeFontSize(f, newFontSize); //将Font隐式转换为FontHandle
//但这个隐式转换会增加错误机会,例如,客户需要拷贝一个Font对象,如下
Font f1(getFont());
.....
FontHandle f2 = f1; //Font 错写成FontHandle,则不会报错,而是将f1隐式转换为其底部的FontHandle,然后才复制它。
这样结果就变成生成了一个FontHandle对象,而客户原意是要拷贝一个Font对象。
故而:
1. APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的办法。
2. 对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换(提供一个显式转换函数,如get)比较安全,但隐式转换(类中重写“()”运算符)对客户比较方便。
条款16 : 成对使用new和delete时要采取相同形式
请记住:
如果你在new表达式中使用[], 必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]>
条款17 : 以独立语句将newed对象置入智能指针
因为在“资源被创建(经由“new”)”和“资源被转换为资源管理对象”两个时间点之间有可能发生异常干扰。考虑如下代码:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priotity); //考虑如下调用
processWidget(new Widget, priority()); //不能通过编译,因为tr1::shared_ptr构造函数需要一个原始指针,但该构造函数是个explicit构造函数,无法进行隐式转换
// 改成以下形式则可通过编译
processWidget(std::tr1::shared_ptr<Widget> (new Widget), priotity());
编译器产出一个processWidget调用码之前,必须首先核算即将被传递的各个实参。于是在调用processWidget之前,编译器必须创建代码,做以下三件事:
(1)调用priority
(2)执行“new Widget"
(3) 调用tr1::shared_ptr构造函数
至于C++编译器以什么次序完成上述三件事呢 ?这个不确定,唯一能保证的是“new Widget”一定先于tr1::shared_ptr构造函数。如果最终以如下顺序执行:
执行“new Widget” --> 调用priority --> 调用tr1::shared_ptr构造函数
现在假设,万一对priority的调用导致异常,那么“new Widget”返回的指针将会遗失,因为它尚未被置入tr1::shared_ptr内,而后者是我们期盼用来防卫资源泄漏的武器。所以,在对processWidget的调用过程中可能引发资源泄漏。因为在“资源被创建(经由“new”)”和“资源被转换为资源管理对象”两个时间点之间有可能发生异常干扰。
解决方案:
使用分离语句,分别写出(1)创建Widget,并将它置入一个智能指针内,(2)再把这个智能指针传给processWidget. 如下:
std::tr1::shared_ptr<Widget> pw (new Widget); //在单独语句以智能指针存储newed所得对象 processWidget(pw, priority); //这个调用动作绝不至于造成泄漏
以上之所以行得通,因为编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内它才拥有那个自由度(参数列表))。
故而:
以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。
Effective C++ —— 资源管理(三)的更多相关文章
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- Effective C++ 笔记三 资源管理
条款13:以对象管理资源 许多资源被动态分配于heap内而后被用于单一区块或函数内.它们应该在控制流离开那个区块或函数时被释放.标准程序库提供的auto_ptr正是针对这种形式而设计的特制产品.aut ...
- Effective C++笔记(三):资源管理
参考:http://www.cnblogs.com/ronny/p/3745098.html 资源:动态分配的内存.文件描述器.互斥锁.图形界面中的字型与笔刷.数据库连接以及网络sockets等, ...
- C++学习书籍推荐《Effective C++ 第三版》下载
百度云及其他网盘下载地址:点我 编辑推荐 <Effective C++:改善程序与设计的55个具体做法(第3版)(中文版)(双色)>前两个版本抓住了全世界无数程序员的目光.原因十分明显:S ...
- Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——7. 消除过期的对象引用
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——10. 重写equals方法时遵守通用约定
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- C++控制台读取和输出函数
c中puts()函数用来向标准输出设备(屏幕)写字符串并换行,其调用方式为,puts(s);其中s为字符串字符(字符串数组名或字符串指针). 功 能: 送一字符串到流stdout中 用 法: int ...
- 4款基于html5 canvas充满想象力的重力特效
今天给大家分享4个物理和重力实验,用来展示 html canvas 的强大.几年前,所有这些实验都必须使用 Java 或 Flash 才能做.在下面这些惊人的例子中,就个人而言,我比较喜欢仿真布料的那 ...
- Dropwizard与Spring Boot比较
在这篇文章中我们将讨论的Java轻量级框架Dropwizard和Spring Boot的相似性和差异. 首先,这是一个选择自由和速度需要,无论你在Dropwizard和Spring Boot选择哪个, ...
- SecureCRT工具
技巧收集: 文本文件内容 复制该行内容yy,p粘贴 2+yy复制两行 dd 删除该行 文件内容搜索 非编辑状态/+查找内容 查找指定行 :+行号
- 关于Cocos2d-x中使用完Blink动作后精灵突然消失的问题的解决
精灵使用Blink 执行完动作之后,消失不见了,原因是闪烁的过程中精灵刚好到空纹理(透明)的那部分,这时候用户通过某种操作中断闪烁动作,导致下个状态的时候,精灵依然停留在空纹理的状态.所以最好在精灵执 ...
- latex之转置符号
$\mathbf{A}^\mathrm{T}$ $\mathbf{A}^\top$ $\mathbf{A}^\mathsf{T}$ $\mathbf{A}^\intercal$ 效果分别为:
- C++类的成员函数的形参列表后面的const
看到(C++ Primer)类的成员函数这里,突然对成员函数形参列表后面的const感到迷惑. 因为书中开始说是修饰隐含形参this的,然后又说是声明该函数是只读的. 大为不解! 翻资料.找人讨论.. ...
- 不可将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较
不可将布尔变量直接与 TRUE.FALSE 或者 1.0 进行比较. 根据布尔类型的语义,零值为“假”(记为 FALSE),任何非零值都是“真”(记为 TRUE). TRUE 的值究竟是什么并没有统一 ...
- 转载:【原译】Erlang性能的八个误区(Efficiency Guide)
转自:http://www.cnblogs.com/futuredo/archive/2012/10/16/2725770.html The Eight Myths of Erlang Perform ...
- erlang工具:Sublime Text的插件
SublimErl :https://github.com/ostinelli/SublimErl (推荐,操作较简单) ...