对象管理资源

今天看了下Effective C++的条款13:以对象管理资源,感觉十分有理,特此做一下笔记。

假设我们使用一个用来描述投资行为的程序库,其中各式各样的投资类型都继承自一个根类 Investment:

//投资类型继承体系中的root class
class Investment{
   ...
};

这里呢,我们进一步假设这个程序库通过一个函数为我们提供Investment对象:

Investment* createInvestment();//指向动态分配对象,这里为了简化,我就不                                    //写参数了

如上所示,createInvestment函数的调用端使用了函数返回的对象后,就有责任删除这个对象。假设,有个函数f()执行这个删除操作:

void f()
{
   IInvestment*pInv = createInvestment();
   ...//这里省去代码的细节
   delete pInv;//释放所指对象
}

这个语句看起来感觉很合理,但是,假如我省略的代码细节里有个return语句,如此一来,我的f()函数在执行了return后就不会触及delete语句,也就不能释放那块内存。

所以,为了确保createInvestment()函数返回的资源总是能被释放,我们需要将资源放到对象内,当控制流离开函数f后,该对象的析构函数就会自动释放那些资源。这种做法就是本节内所讨论的问题:把资源放进对象内,依靠“C++析构函数自动调用机制”来确保资源被释放。

许多资源被动态分配于堆区(heap segment)内,而后就在某个函数或者区块内被使用。这些资源需要在函数调用完成或者离开区块的时候被释放。针对这个问题,C++标准程序库为我们提供了一些解决办法,比如auto_ptr(俗称智能指针)。auto_ptr是一个栈对象,他的析构函数自动对其对象调用delete函数,达到一个释放资源的作用。下面介绍一下auto_ptr避免f()函数内内存泄漏问题:

void f()
{
   std::auto_ptr<Investment>pInv(createInvestment());
   ...//经由auto_ptr的析构函数自动释放pInv所指对象的内存(Effective //C++里说是删除pInv,可能是因为翻译的问题吧,感觉不对)
}

于是,这一个简单的小例子就阐明了“以对象管理资源”的两个关键的想法:

  1. 获得资源后立刻放进管理对象内。
    上述代码中, createInvestment()函数的返回值直接作为了auto_ptr的对象的初值。实际上,这在C++里面有个形象的说法,就是RAII(Resource Acquisition Is Initialization,资源获取即初始化)。

  2. 管理对象运用析构函数确保资源被释放。简而言之:只要对象离开作用域(比如离开函数的大括号)就自动调用析构函数,也就释放了资源(当然如果释放过程中出现了异常,就是比较麻烦的问题了,这一点放在后面去讨论,这里只关心资源释放的主要功能)。

    特别注意的是,由于auto_ptr被销毁的时候回自动释放他所指向的那块内存,所以一定不能让多个auto_ptr同时指向同一个对象。否则,就会发生未定义的行为。为了预防这种问题,请大家像我一样遵循如下的规则:
    如果通过拷贝构造函数或者析构函数复制auto_ptr,他们就会变成null,而复制所得的指针将得到资源的唯一所有权。
    这一个规则我在这里用代码给大家进行一下简单的演示:


std::auto_ptr<Investment>pInv(createInvestment());
std::auto_ptr<Investment>pInv2(pInv);//情况1:调用拷贝构造函数
pInv1 = pInv2;//情况2:调用赋值函数

如上所示,情况1和2下都会产生同一个问题,pInv1和 pInv2都会变成null。

所以,为了解决这种诡异的现象,C++为我们提供了一个替代方案,也就是所谓的“引用计数型智慧指针”(RCSP,下面我都用这个词来简写)。TR1的shared_ptr就是常用的RCSP。

void f()
{
  ...
  std::tr1::shared_ptr<Investment>pInv(createInvestment());
  //当f执行完毕,离开大括号的时候,就会自动调用shared_ptr的析构函数,自动释放pInv所指的对象内存
}

这样的写法看似与auto_ptr类似,但是实际上,使用shared_ptr不会出现前面所说的那种置null现象,如下代码就可以看出差别:

void f()
{
    ...
    std::tr1::shared_ptr<Investment>pInv(createInvestment());
    std::tr1::shared_ptr<Investment>pInv2(pInv);
    pInv = pInv2;//都没有置null
}

当然,除了以上的一些特点,还需要注意的是,auto_ptr与tr1::shared_ptr在析构函数内调用的都是delete,而不是delete[],所以,请注意:动态分配数组的时候,请勿使用auto_ptr与tr1::shared_ptr(尽管编译器不会报错,如果你非要用含有诸如auto_ptr与tr1::shared_ptr的类,那么请你去学一下Boost)。

小节:

  1. 为了防止内存泄漏,请务必使用RAII对象。他们会在构造函数中获取资源,在析构函数中delete资源。
  2. 常用的RAII对象是std::auto_ptr和std::tr1::shared_ptr,后面这个是较佳的选择。还有就是注意二者在调用拷贝构造函数和赋值函数时的区别。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值