四:资源管理

不论哪一种资源,重要的是,当你不再使用它时,必须将其归还给系统。

条款十三:以对象管理资源

我们先来看一个例子以说明为什么要这么做:
我们使用一个用来模拟投资行为的程序库,其中有一个基类Investment:

class Investment{...};

我们通过一个工厂函数(factory function)供应某特定的Investment对象:

//返回指针,指向Investment继承体系的动态分配对象,调用者有责任删除它
Investment* creatInvestment();

好,这里把资源回收的责任交给调用者来delete回收,现在我们来用用看,会有什么问题:

void f(){
    Investment* pInv = creatInvestment();
    ...
    delete pInv;
}

看着好像挺对的,调用者没有忘记delete,有几种情况会导致delete语句没有执行从而导致资源泄漏:
例如过早的遇到了return语句,或者前面抛出了异常

所以啊,单纯依赖delete语句是行不通的

下面是金玉良言啦:
为确保creatInvestment返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f,该对象的析构函数会自动释放那些资源,也就是说,我们把资源放进对象,就可以依赖C++ 的析构函数自动调用机制确保资源被释放

void f(){
    auto_ptr<Investment> pInv(creatInvestment());
}

由于 auto_ptr 被销毁时会自动删除它所指之物,不能多个 auto_ptr 指向一个对象,所以它有个性质:当通过拷贝函数复制它时,原指针变成null,目标指针将取得资源的唯一拥有权

这个auto_ptr使用起来还是有局限的,它不允许多个指针指向同一个,所以引入了智能指针,带引用计数功能,但它也有问题:环状引用

二者还有一个共同的局限,它们在析构函数中调用的都是delete而不是delete[],所以在动态分配的array上不要使用它们,那么array怎么办呢?别担心,你基本用不到,因为可以用vector和string替代

小结

  • 为防止资源泄漏,请使用auto_ptr和智能指针对象管理资源,它们在构造函数中获得资源并在析构函数中释放资源
  • 两个常用的指针管理对象分别是引用计数型和auto_ptr,各有优缺点,还有共同局限

条款十四:在资源管理类中小心拷贝行为

我们来直接看例子:
我们使用C API函数处理类型为Mutex的互斥器对象:

void lock(Mutex* pm) //锁定pm所指的互斥器
void unlock(Mutex* pm) //将互斥器解除锁定

我们用对象来管理资源:

class Lock{
public:
    explicit Lock(Mutex* pm) : mutexPtr(pm) {lock(mutexPtr);} //获得资源
    ~Lock() { unlock(mutexPtr);} //释放资源
private:
    Mutex *mutexPtr;
};

用户使用也符合规范:

Mutex m; //定义需要的互斥器
{
    Lock m1(&m); //构造函数获得资源
}//析构函数释放

这一切都没问题,都是按照之前提款进行,但是现在加这么一句呢:

Lock ml1(&m); //锁定m
Lock ml2(ml1); //复制

这个问题就是:当一个资源管理类对象被复制,会发生什么
一般有两个选择:

  1. 禁止复制,如果在该类中复制是不合理的,就应该禁止:
    Class Lock: private Uncopyable{...};
    
  2. 对底层资源使用引用计数
    有一个问题是,引用型智能指针在引用次数为0时是删除,这里只想要unlock,幸好我们可以自定义引用型智能指针的删除器(函数或函数对象):
    class Lock{
    public:   
     explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) {lock(mutexPtr.get());} 
     //没有析构是因为这里可以依赖编译器默认生成的去调用删除器
    private:
     shared_ptr<Mutex> mutexPtr;
    };
    

小结:

普遍而常见的资源管理类拷贝行为是:禁止拷贝或引用计数法

条款十五:在资源管理类中提供对原始资源的访问

这部分并不是非常符合一贯的设计,只是对现实的屈服,所以我不多写了

小结

  • API往往要求对原始资源的访问,所以每个资源管理类应该提供一个去的其所管理资源的方法,智能指针有get()方法返回普通指针,普通指针可以指向原始资源以提供访问
  • 对原始资源的访问可以显式转换get(),也可以提供隐式转换函数
    operator FontHandle() const {return f;}
    
    显式安全,隐式方便

条款十六:成对使用new和delete时要采取相同的形式

小结:

游戏规则很简单:如果你调用new时使用[],delete也要用;new没有[],delete也没有

以独立语句将newed对象置入智能指针

我们来看一个不好的例子:

processWidget(shared_ptr<Widget>(new Widget), priority())

这里的函数参数很复杂,而且在将对象置入智能指针中间可能插入了priority函数,万一该函数报错了,那就gg了,我们一向倡导的以资源管理类来管理没得到实现,于是正确的写法是:

shared_ptr<Widget> pw(new Widget); //单独语句以智能指针存储new的对象
processWidget(pw, priority());

这样priority就插不进去了

小结

以独立语句将new出来的对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏

全部评论
6
点赞 回复 分享
发布于 2017-06-13 08:35

相关推荐

秋盈丶:后续:我在宿舍群里和大学同学分享了这事儿,我好兄弟气不过把他挂到某脉上了,10w+阅读量几百条评论,直接干成精品贴子,爽
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务