• 对象管理资源


    今天看了下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,后面这个是较佳的选择。还有就是注意二者在调用拷贝构造函数和赋值函数时的区别。
  • 相关阅读:
    linux将home目录扩容到根目录
    Daily Build
    H公司数据同步的总结
    VB2010新特性之——标识语言版本的新命令行选项/langversion (Visual Basic)
    Linux安装Jemalloc
    Lnmp切换PHP版本
    Server2008通过bat命令自动定时备份MySQL数据库
    IIS 安装AspNetCoreModule托管模块
    JavaScript 学习笔记——Math属性及其方法
    js完美多物体运动框架(缓冲运动)
  • 原文地址:https://www.cnblogs.com/chankeh/p/6850063.html
Copyright © 2020-2023  润新知