• 条款13:以对象管理资源


    假设类Investment是各种关于投资的基类,股票类Stock继承自Investment。有一个工厂函数可以返回一个具体的Investment对象:

     1 #include<iostream>
     2 
     3 using namespace std;
     4 
     5 class Investment              // 关于投资的基类
     6 {
     7 public:
     8     virtual ~Investment();
     9 };
    10 Investment::~Investment(){ }
    11 
    12 class Stock : public Investment     // 股票类,继承自Investment
    13 {
    14 
    15 };
    16 
    17 Investment* createInvestment()      // 工厂函数,返回一个派生类对象(此处为了简化,其实最好定义一个工厂类做这个工作)
    18 {
    19     return new Stock;
    20 }
    21 
    22 void use()                          // 使用工厂函数返回的对象,完成工作后删除这个对象
    23 {
    24     Investment* pInv = createInvestment();
    25     // ...
    26     delete pInv;
    27 }
    28 
    29 int main()
    30 {
    31     use();                          // 正常运行
    32 
    33     return 0;
    34 }

    正常情况下,代码正常运行。现在考虑这样一个事实:如果在// ...处出现了return语句,那么是不是delete就无法执行了,如此就造成了内存泄漏。很多情况下,即使函数设计非常严密,当前不会不出现这种类似情况,但是如果后期维护人员为了调试出现的问题,自行加入了导致delete不能运行的语句块呢?也就是说delete不能执行的情况总会发生,幸好我们还有如下方法可以避免这个问题:

    把需要被释放的资源放入管理对象中,这样便可以依靠C++的“析构函数自动调用机制”确保资源被释放。

    注意:

    此方法适合管理动态分配的heap对象。

    auto_ptr是个pointer-like对象,就是所谓的智能指针,会在一个模块结束后自动调用所指对象的delete,示例如下:

    1 void use()                          // 使用工厂函数返回的对象,完成工作后删除这个对象
    2 {
    3     auto_ptr<Investment> pInv(createInvestment());  // 使用auto_ptr后不用显式调用delete了
    4     // ...
    5 }

    上述代码体现了两个关键想法:

    1 在获得资源后立刻放进管理对象中:即用指向具体对象的指针来初始化auto_ptr。其思想也被称为“资源获得时机便是初始化时机: Resource Acquisition Is Initialization: RAII

    2 管理对象运行析构函数确保资源的到释放:一旦当对象离开其作用域时,对象的析构函数会被自动调用,再也不用担心内存泄漏了?当然也不是,如果析构函数出现问题呢,这就是另一个话题了。

     

    我们总是在不经意间将同一对象delete不止一次,auto_ptr也考虑到了这个问题,其规定:如通过copy构造函数或者copy assignment操作符函数copy它们,那么被copy的那个指针就会变为null,就是说确保永远只有一份auto_ptr,就像如下:

    1 void use()                          // 使用工厂函数返回的对象,完成工作后删除这个对象
    2 {
    3     auto_ptr<Investment> pInv(createInvestment());  // 使用auto_ptr后不用显式调用delete了
    4     // ...
    5     auto_ptr<Investment> pInv2(pInv);               // 现在pInv2指向Stock对象,pInv为null
    6     pInv = pInv2;                                   // 现在pInv指向Stock对象,pInv2为null
    7 }

    但是对于某些类,比如STL容器,其要求可以同时有多个指针指向同一对象,此时,auto_ptr显得有些力不从心了,但是我们又有一个“带有计数功能的智能指针:reference-counting smart pointer: RCSP”,其可以计数共有多少指针指向同一对象,只有在计数为0时才自动释放该对象,那么use()就可以如下设计:

    1 void use()                          // 使用工厂函数返回的对象,完成工作后删除这个对象
    2 {
    3     tr1::shared_ptr<Investment> pInv(createInvestment());
    4     tr1::shared_ptr<Investment> pInv2(pInv);    // pInv和pInv2同时指向Stock对象
    5     pInv = pInv2;                               // 同上,没有任何改变
    6 }

    注意:

    auto_ptr和tr1::shared_ptr二者的析构动作都是执行delete而不是delete[],就是说如果动态分配的是数组,那么使用智能指针不会释放全部资源,C++并没有针对动态分配的数组做智能化释放处理,那是因为vector和string可以完全取代数组的工作。

     

    最后有一个小点,如果我们想要使用智能指针所指向的原始指针,如何做呢?

    有两种方式:

    1 使用显式转换:get()

    Investment* pInv2 = pInv.get();

    2 自己设计隐式转换函数

    一般而言,显式转换虽然麻烦,但是比较安全;隐式转换虽然比较方便,但是有时会造成难以发现的错误。

  • 相关阅读:
    水晶报表开发
    ASP.NET页面刷新方法总结
    DataList获取当前ID
    flexviewer让指定slid值对于的图层可见
    WKT
    消息的分类
    MFC画图
    MFC画文字DrawText,GetTextExtent,GetTextMetrics
    djvu是什么
    代码提示插件 Visual Assistxv
  • 原文地址:https://www.cnblogs.com/benxintuzi/p/4532883.html
Copyright © 2020-2023  润新知