zoukankan      html  css  js  c++  java
  • 读书笔记_Effective_C++_条款十三:以对象管理资源

    从这个条款开始,就进入到资源管理部分了。资源管理往往是大型项目的一个难点,也是重中之重,看到一些编程规范,都是将资源管理的规范列为高优先级的重点。

    管理资源的最好方法其实是预防,而好的预防方法就是尽量不去使用C/C++的原生指针,这些指针像幽灵一样,一个“忘记”,就是一个隐患。当项目小的时候,这些隐患看不出来,但当研发一个拥有上万级用户的产品时,服务器对很多人同时运行含有隐患的代码,这个隐患就会爆发,导致内存不足而崩溃。

    举个例子,初学者常常这样写:

    1 int *p = new int();
    2 3 delete p;

    这个当然是OK的,内存可以回收,但万一中间的过程超过数十行,你还能记住去delete p吗?

    有些同学可能会这样做,每一个new之后,先写好delete,再往中间插代码,但如果遇到这样的情况:

    1 int* GetResource()
    2 {
    3  return new int(10);
    4 }

    这个函数负责生成资源,并将之返回给上一层,因为这个资源是要在上一层用的,所以在函数内还不能直接delete,这可就麻烦了。程序员们可要随时记挂着这个内存,在不再需要的时候赶紧释放了。但一切可能出错的地方真的会出错,代码一多,记性就不那么好了,很容易出现忘记delete的事情。

    那么有没有什么好的方法,可以让我们痛快地管理资源呢?这就牵涉到智能指针的问题了。常用的智能指针有memory头文件里面的auto_ptr,还有boost库里的shared_ptr。两者的实现机制不同,但功能是类似的,就是能自动管理资源。他们体现的思想就是RAII(Resources Acquisition Is Initialzeation 资源取得的时机便是初始化时机)。

    举个形象的例子就是:

    auto_ptr<int> ap(new int(10));

    或者是:

    shared_ptr<int> sp(new int(10));

    可以看到,这里根本就没有出现原生指针,而是直接将new出来的内存交给了一个“管理者”。这个管理者可以是auto_ptr,也可以是shared_ptr,他们的存在,使得程序员可以真的忘记delete了,当ap或者sp的生命周期结束时,他们会将资源释放出来,交还给系统。比如:

    1 void fun()
    2 {
    3 auto_ptr<int> ap(new int(10);
    4 *ap = 20;
    5 }
    6 // ap 在fun()结束后会自动回收掉

    auto_ptr和shared_ptr的区别在于管理方法不同。

    auto_ptr这个管理者是一个很霸气的boss,他只想独管资源,而不允许其他管理者插手,否则他就退出管理,把这个资源交给另一方,自己再也不碰了。

    举个例子:

    auto_ptr<int> boss1(new int(10)); // 这个时候boss1对这个资源进行管理

    auto_ptr<int> boss2(boss1); // 这个时候boss2要插手进来,boss1很生气,所以把管理权扔给了boss2,自己就不管事了。所以boss2持有这个资源,而boss1不再持有(持有的是null)。

    所以同一时刻,只有一个auto_ptr能管理相同的资源。

    这里还是有必要解释一下这句话的,比如:

    1 auto_ptr<int> boss1(new int(10)); 
    2 auto_ptr<int> boss2(new int(10));

    boss1还持有资源吗?答案是有的,因为new执行了两次,虽然内存空间里的初始值是一样的,但地址并不同,所以不算相同的资源。

    再来,为了把问题讲清楚,这里就让原生指针再出场一下:

    1 int *p = new int(10);
    2 auto_ptr<int> boss1(p);
    3 auto_ptr<int> boss2(p);

    boss1还持有资源吗?

    答案是当程序运行到第三句的时候,就弹出assertion failed的红叉,程序崩溃了,为什么会这样呢?因为p所指向的是同一块资源,所以boss2发出管理手段后,boss1肯定要有动作的。第三句话调用boss2的构造函数,但构造函数中boss2识别出来p所指向的资源已经被占用了,所以会assertion failed。

    事实上,这种原生指针和智能指针混用的程序是非常不好的,如果不使用原生指针,上面的崩溃问题就不会发生。

    我自己根据auto_ptr的思想,仿写了一下这个类,有兴趣的读者可以看看,其实并不复杂:

     1 #ifndef MY_AUTO_PTR_H
     2 #define MY_AUTO_PTR_H
     3 
     4 // 自定义的智能指针
     5 #include <iostream>
     6 using namespace std;
     7 
     8 template <class T>
     9 class MyAutoPtr
    10 {
    11 private:
    12     T *ptr;
    13     static void swap(MyAutoPtr &obj1, MyAutoPtr &obj2)
    14     {
    15         // 细到交换成员变量
    16         std::swap(obj1.ptr, obj2.ptr);
    17     };
    18 
    19 public:
    20     explicit MyAutoPtr(T *p = NULL): ptr(p){}
    21     
    22     MyAutoPtr(MyAutoPtr& p)
    23     {
    24         ptr = p.ptr;
    25         p.ptr = 0;
    26     }
    27 
    28     MyAutoPtr& operator= (MyAutoPtr& p)
    29     {
    30         if(&p != this)
    31         {
    32             MyAutoPtr temp(p);
    33             swap(*this, temp);
    34         }
    35         return *this;
    36     }
    37     
    38     ~MyAutoPtr()
    39     {
    40         delete ptr;
    41         ptr = 0;
    42     }
    43 
    44     T& operator* () const
    45     {
    46         return *ptr;
    47     }
    48 
    49     T* operator-> () const 
    50     {
    51         return ptr;
    52     }
    53 
    54     T* get() const
    55     {
    56         return ptr;
    57     }
    58 
    59 
    60     friend ostream& operator<< (ostream& out, const MyAutoPtr<T>& obj)
    61     {
    62         out << *obj.ptr;
    63         return out;
    64     }
    65 
    66 };
    67 
    68 
    69 #endif /* MY_AUTO_PTR_H */
    MyAutoPtr

    因为auto_ptr常常发生管理权的交付,所以用作形参的时候一定要小心:

    若有一个函数是:void fun(auto_ptr<int> ap){}

    在主函数中调用fun(main_ap);

    main_ap就会失去管理权了。

    正是因为这个特性,所以auto_ptr不支持STL容器,因为容器要求“可复制”,但auto_ptr只能有一个掌握管理权。另外,auto_ptr也不能用于数组,因为内部实现的时候,用的是delete,而不是delete[]。

    shared_ptr则顾名思义,他是一个分享与合作的管理者,他的内部实现是引用计数型的,每多一次引用,计数值就会+1,而每一次引用的生命周期结束时,计数值就会-1,当计数值为0的时候,说明内存没有管理者管理了,最后一个管理这个内存的管理者就会将之释放。

    再回到我们之前的例子:

    1 shared_ptr<int> boss1(new int(10)); // 这个时候boss1对这个资源进行管理
    2 shared_ptr<int> boss2(boss1); // boss2携手boss1对这个资源进行管理

    注意这个时候boss1并没有交出管理权,boss2的加入只对对象内部的计数指针造成了影响,在外部就像什么也没发生一样,可以使用boss1和boss2来管理资源。

    下面写上我自己写的shared_ptr,也很简单的(注意swap函数要自己去写,不能调用系统自带的,否则会陷入赋值运算符的无限递归中):

     1 #ifndef MY_SHARED_PTR_H
     2 #define MY_SHARED_PTR_H
     3 
     4 #include <iostream>
     5 using namespace std;
     6 
     7 template <class T>
     8 class MySharedPtr
     9 {
    10 private:
    11     T *ptr;
    12     size_t *count;
    13     static void swap(MySharedPtr& obj1, MySharedPtr& obj2)
    14     {
    15         std::swap(obj1.ptr, obj2.ptr);
    16         std::swap(obj1.count, obj2.count);
    17     }
    18 
    19 public:
    20     MySharedPtr(T* p = NULL): ptr(p), count(new size_t(1)){}
    21 
    22     MySharedPtr(MySharedPtr& p): ptr(p.ptr), count(p.count)
    23     {
    24         ++ *p.count;
    25     }
    26 
    27     MySharedPtr& operator= (MySharedPtr& p)
    28     {
    29         if(this != &p && (*this).ptr != p.ptr)
    30         {
    31             MySharedPtr temp(p);
    32             swap(*this, temp);
    33         }
    34         return *this;
    35     }
    36 
    37     ~MySharedPtr()
    38     {
    39         reset();        
    40     }
    41 
    42     T& operator* () const
    43     {
    44         return *ptr;
    45     }
    46 
    47     T* operator-> () const 
    48     {
    49         return ptr;
    50     }
    51 
    52     T* get() const 
    53     {
    54         return ptr;
    55     }
    56 
    57     void reset()
    58     {
    59         -- *count;
    60         if(*count == 0)
    61         {
    62             delete ptr;
    63             ptr = 0;
    64             delete count;
    65             count = 0;
    66             //cout << "真正删除" << endl;
    67         }
    68     }
    69 
    70     bool unique() const
    71     {
    72         return *count == 1;
    73     }
    74 
    75     size_t use_count() const 
    76     {
    77         return *count;
    78     }
    79 
    80 
    81     friend ostream& operator<< (ostream& out, const MySharedPtr<T>& obj)
    82     {
    83         out << *obj.ptr;
    84         return out;
    85     }
    86 
    87 };
    88 
    89 #endif /* MY_SHARED_PTR_H */
    MySharedPtr

    好,最后总结一下:

    1. 为了防止资源泄漏,使用RAII思想,它们在构造函数中获得资源,并在析构函数中释放资源

    2. 两个常用的RAII类是shared_ptrauto_ptr,但前者一般是更佳的选择,因为其copy行为比较直观,若选择auto_ptr,复制动作会使它指向null。

  • 相关阅读:
    RedGlove 权限管理系统(1)权限概述
    SPQuery查询语法简要说明
    DataGridView使用技巧大全
    实战asp.net MVC+ADO.NET EntityFramework
    RedGlove 权限管理系统(2)功能模块设计
    C# 如果何从线程中操作控件
    PageHelper 类 和 ValidateHelper 类
    字符串帮助类
    XMLHelper 类
    Android中截取当前屏幕的功能
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/3086501.html
Copyright © 2011-2022 走看看