zoukankan      html  css  js  c++  java
  • 智能指针原理及实现(1)shared_ptr

    0、异常安全

    C++没有内存回收机制,每次程序员new出来的对象需要手动delete,流程复杂时可能会漏掉delete,导致内存泄漏。于是C++引入智能指针,可用于动态资源管理,资源即对象的管理策略。

    使用 raw pointer 管理动态内存时,经常会遇到这样的问题:

    • 忘记delete内存,造成内存泄露。
    • 出现异常时,不会执行delete,造成内存泄露。

    下面的代码解释了,当一个操作发生异常时,会导致delete不会被执行:

    1 void func() 
    2 {
    3     auto ptr = new Widget;
    4     // 执行一个会抛出异常的操作
    5     func_throw_exception();
    6     
    7     delete ptr;
    8 }

    在C++98中,为了写出异常安全的代码,代码经常写的很笨拙,如下:

     1 void func() 
     2 {
     3     auto ptr = new Widget;
     4     try {
     5         func_throw_exception();
     6     }
     7     catch(...) {
     8         delete ptr;
     9         throw; 
    10     }
    11     delete ptr;
    12 }

    使用智能指针能轻易写出异常安全的代码,因为当对象退出作用域时,智能指针将自动调用对象的析构函数,避免内存泄露。

    一、智能指针shared_ptr

    智能指针主要有三种:shared_ptrunique_ptrweak_ptr

     shared_ptr

    shared_ptr是最常用的智能指针(项目中我只用过shared_ptr)。shared_ptr采用了引用计数器,多个shared_ptr中的T *ptr指向同一个内存区域(同一个对象),并共同维护同一个引用计数器。shared_ptr定义如下,记录同一个实例被引用的次数,当引用次数大于0时可用,等于0时释放内存。

    注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。循环引用在weak_ptr中介绍。

    1 temple<typename T>
    2 class SharedPtr {
    3 public:
    4    ... 
    5 private:
    6     T *_ptr;
    7     int *_refCount;     //should be int*, rather than int
    8 };

    shared_ptr对象每次离开作用域时会自动调用析构函数,而析构函数并不像其他类的析构函数一样,而是在释放内存是先判断引用计数器是否为0。等于0才做delete操作,否则只对引用计数器左减一操作。

    1 ~SharedPtr()
    2 {
    3     if (_ptr && --*_refCount == 0) {
    4         delete _ptr;
    5         delete _refCount;
    6     }
    7 }

    接下来看一下构造函数,默认构造函数的引用计数器为0,ptr指向NULL:

    1 SharedPtr() : _ptr((T *)0), _refCount(0)
    2 {
    3 }

    用普通指针初始化智能指针时,引用计数器初始化为1:

    1 SharedPtr(T *obj) : _ptr(obj), _refCount(new int(1))
    2 {
    3 } //这里无法防止循环引用,若我们用同一个普通指针去初始化两个shared_ptr,此时两个ptr均指向同一片内存区域,但是引用计数器均为1,使用时需要注意。

    拷贝构造函数需要注意,用一个shared_ptr对象去初始化另一个shared_ptr对象时,引用计数器加一,并指向同一片内存区域:

    1 SharedPtr(SharedPtr &other) : _ptr(other._ptr), _refCount(&(++*other._refCount))
    2 {
    3 }

    赋值运算符的重载

    当用一个shared_ptr<T> other去给另一个 shared_ptr<T> sp赋值时,发生了两件事情:

    一、sp指针指向发生变化,不再指向之前的内存区域,所以赋值前原来的_refCount要自减

    二、sp指针指向other.ptr,所以other的引用计数器_refCount要做++操作。

     1 SharedPtr &operator=(SharedPtr &other)
     2 {
     3     if(this==&other)
     4         return *this;
     5         
     6     ++*other._refCount;
     7     if (--*_refCount == 0) {
     8         delete _ptr;
     9         delete _refCount;
    10     }
    11         
    12     _ptr = other._ptr;
    13     _refCount = other._refCount;
    14     return *this;
    15 }    

    定义解引用运算符,直接返回底层指针的引用:

    1 T &operator*()
    2 {
    3     if (_refCount == 0)
    4         return (T*)0;
    5         
    6     return *_ptr;
    7 }

    定义指针运算符->

    1 T *operator->()
    2 {
    3     if(_refCount == 0)
    4         return 0;
    5         
    6     return _ptr;
    7 }

    二、测试

    1 int main(int argc, const char * argv[])
    2 {
    3     SharedPtr<string> pstr(new string("abc"));
    4     SharedPtr<string> pstr2(pstr);
    5     SharedPtr<string> pstr3(new string("hao"));
    6     pstr3 = pstr2;
    7     
    8     return 0;
    9 }

    为了让测试结果更明显,我在方法中加入了一些输出,测试结果如下:

     源码链接:https://github.com/guhowo/test/tree/master/cplus/SharedPtr

    思考

    1、本文这种写法不是线程安全的,是吧?

    2、boost中的shared_ptr线程安全吗?

  • 相关阅读:
    C. Shaass and Lights 解析(思維、組合)
    D. Binary String To Subsequences(队列)(贪心)
    CodeForces 1384B2. Koa and the Beach (Hard Version)(贪心)
    CodeForces 1384B1. Koa and the Beach (Easy Version)(搜索)
    CodeForces 1384C. String Transformation 1(贪心)(并查集)
    CodeForces 1384A. Common Prefixes
    POJ-2516 Minimum Cost(最小费用最大流)
    POJ3261-Milk Patterns(后缀数组)
    HDU-1300 Pearls(斜率DP)
    HDU-4528 小明系列故事-捉迷藏(BFS)
  • 原文地址:https://www.cnblogs.com/howo/p/8468713.html
Copyright © 2011-2022 走看看