effective c++ Item13~17 资源管理-学习整理
概括:
Item 13~17 介绍了c++资源管理方面的内容。这5个Item可以分2类,第1类是标准库提供的智能指针介绍(Item13)和使用细节的讨论(Item17);第2类指导如何设计一个资源管理类(Item14,Item15)
详细来看:
Item 13 建议用对象管理资源防止内存泄露
(1)常见姿势:我们习惯于使用new和delete来申请和释放内存。如下:
class Investment {...}
Investment* createInvestment();
void f()
{
Investment *pInv = createInvestment();
... //use pInv
delete pInv; //release object
}
但是这样的姿势存在潜在的问题:程序没有走到delete pInv的地方(提前return,循环里goto了,异常早于delete被catch处理)。这些原因都造成内存泄露,所以这种方式靠不住。
(2)建议姿势:为了确保createInvestment()返回的资源总能被释放,将资源放入类中,让类的析构函数在离开f 被调用时自动释放资源。标准库提供:auto_ptr,shared_ptr可以帮我们做到这一点。
void f()
{
std::auto_ptr<Investment> pInv(createInvestment());
...
}
(3)注意点:a.复制语境限制了std::auto_ptr,所以尽量使用std::shared_ptr少用auto_ptr;
b.别在动态分配数组上使用std::auto_ptr或者std::shared_ptr。两者都在其析构函数中做delete操作而不是delete []动作。此外c++没有给动态分配的数组设计类似智能指针这样的东西。
Item 14 自己设计之“在资源管理类中小心copying行为”
哪些资源不适合使用c++标准库提供的智能指针呢?非heap-based资源,例如mutex锁。我们希望在mutex资源上也建立起一个类似智能指针的智能类来管理mutex资源,让其在离开作用域的时候能够自行解锁。
我们按照下边方式使用Lock
设计这个资源管理类的过程中,我们遇到的问题来了:copy行为如何定义
根据std::auto_ptr和std::shared_ptr,已经提供给我们的两种解决思路:
(1)转移底部资源的拥有权——场合少,使用频率很低
(2)对底层资源采用“引用计数法”——希望保有资源直到最后一个使用者被销毁的场合
这种解决方案一个简洁的方法是让我们设计的这个资源管理类直接“is-implemented-in-terms-of” std::shared_ptr(由std::shared_ptr实现),并且改变std::shared_ptr的第二个参数(删除器)
另外的两种解决思路:
(3)直接禁止复制——复制动作不合理的场合
(4)复制底部资源(xx)
Item 15 自己设计之“在资源管理类中提供对原始资源的访问”
该问题的场景是:如果我们用别人设计的接口,而这个接口参数是个裸指针,那么我们的资源管理对象就不得不提供一个接口来提供裸资源,否则,像下图的调用方式无法通过编译(传递std::shared_ptr 对象给Investment*)
解决这个问题的办法有两种:
(1)给我们设计的资源管理类增加一个get接口,返回智能指针内部的原始指针
(2)隐式转换接口
例如,对于我们设计的类Font,FontHandle是我们需要管理的裸资源
按照上述两种方式,获取裸资源的方法分别如下:
(1)增加get接口获取裸资源(显示转换)
优点:安全,不容易出错
(2)为Font提供一个隐式转换到它的FontHandle的转换函数
优点:调用自然而简单
缺点:增加了出错的概率
缺点的例子:f1被销毁时,字体将被RAII类释放,f2则处于悬挂状态。
Item 16 new和delete使用时要成对且采取相同的形式
这个没什么好说的,new申请的内存用delete释放;new 申请的数组存放的资源,用delete []释放。特别注意的是使用typedef动作。
Item 17 在一个独立的语句中将new出来的对象存入智能指针
该Item旨在告诉我们一些关于std::shared_ptr的细节,使用的过程中我们要注意这些设计的细节,否则会陷入无谓的纠结
(1)std::shared_ptr的构造函数是explicit修饰的。它只能显示构造,不支持隐式转换,所以我们用如下方式来使用就不对
processWidget(new Widget, priority());
虽然我们的processWidget的函数申明如下:
int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);
编译报错,因为我们的std::shared_ptr<Widget>不支持从Widget* 的原始指针到std::shared_ptr<Widget>的隐式转换。我们必须显示的构造这个智能指针,如下:
void processWidget(std::shared_ptr<Widget>(new Widget), priority());
(2)上述过程只描述了本Item的第一个需要注意的地方,没有解释为什么要用一个独立的语句来做这件事。
原因:c++的编译器看到这个函数,在产出它的调用码前,首先进行参数核算,其顺序和java、c#不同,它不是按照顺序的方式执行的,这个函数的定义,编译器会分成3个步骤执行:
第一步:调用priority
第二步:执行new Widget表达式
第三步:调用std::shared_ptr构造函数
这样好像没有什么问题,但是如果编译器也可能按照这个顺序执行:
第一步:执行new Widget表达式
第二步:调用priority (异常恰巧出现)
第三步:调用std::shared_ptr构造函数
这样就出现问题了,第一步申请的内存还没来得及进入第三步std::shared_ptr的构造过程就出现异常导致资源泄漏。因此调用这个函数的过程中可能引发资源泄漏,建议如下方式: