C++智能指针: shared_ptr 实现详解

编程语言:C++ 同时被 2 个专栏收录
64 篇文章 1 订阅
104 篇文章 0 订阅

shared_ptr描述

声明

shared_ptr属于C++11特性中新加的一种智能指针,它的实现方式是模版类,头文件<memory>
template <class T> class shared_ptr
所以使用shared_ptr的声明方式即为
std::shared_ptr<type_id> statement 其中type_id为类型(可以是基本数据类型或者类),statement即为模版类对象

作用

shared_ptr 的理解如下:

  • 使用一种叫做RAII(Resource Acquisition Is Initialization)的技术作为实现基础:
    在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源
    raii技术的好处是:
    • 不需要显式释放资源
    • 对象所拥有的资源在其生命周期内始终有效
  • 防止忘记调用delete释放内存或者程序异常退出时没有释放内存。
  • 同时它能够将值语义转为引用语义(即shared_ptr可以让多个指针指向相同的对象,共享同一块地址空间),shared_ptr使用引用技术方式来统计当前对象被引用的次数,每一次执行析构函数,引用计数就会-1,当引用计数减为0时自动删除所指对象,回收对象空间。

原理实现

常用操作以及源码实现如下:
类声明如下

temple<typename T>
class SharedPtr {
public:
   ... 
private:
    T *_ptr;
    int *_refCount;     //这里使用int型指针是为了保证拷贝构造时同一个地址空间的引用计数增加
};
  • constructor构造函数,初始化的时候默认引用计数为0
    SharedPtr() : _ptr((T *)0), _refCount(0)
    {}
    
    在使用普通指针初始化两个shared_ptr的时候,两个shared_ptr的引用计数都为1
    SharedPtr(T *obj) : _ptr(obj), _refCount(new int(1))
    {
    } 
    
    在进行拷贝构造的时候,使用shared_ptr去初始化另一个shared_ptr的时候引用计数会+1
    SharedPtr(SharedPtr &other) : _ptr(other._ptr), _refCount(&(++*other._refCount))
    {
    }
    
  • destructor析构函数,析构的时候在指针不为空且引用计数为0的时候释放空间
    ~SharedPtr()
    {
        if (_ptr && --*_refCount == 0) {
            delete _ptr;
            delete _refCount;
        }
    }
    
  • operator = 当使用一个shared_ptr给另一个shared_ptr赋值的时候这里需要注意
    1. 由于指针指向发生变化,原来的_ptr指针的引用计数要–,且当达到了0的时候要注意回收原来指针的空间
    2. _ptr又指向了新的_ptr,则新的_ptr指针的引用计数要++
    SharedPtr &operator=(SharedPtr &other)
    {
        if(this==&other)
            return *this;
        
        //新指针引用计数要++  
        ++*other._refCount;
    
    	//原指针引用计数要--,如果为0,则释放空间
        if (--*_refCount == 0) {
            delete _ptr;
            delete _refCount;
        }
           
        //重新进行指向 
        _ptr = other._ptr;
        _refCount = other._refCount;
        return *this;
    }
    
  • operator* 解引用运算符,直接返回底层指针的引用,即共享的地址空间内容
    T &operator*()
    {
        if (_refCount == 0)
            return (T*)0;
            
        return *_ptr;
    }
    
  • operator ->指针运算符
    T *operator->()
    {
        if(_refCount == 0)
            return 0;
            
        return _ptr;
    }
    

函数使用

主要案例如下
在这里插入图片描述

  • 构造函数 constructor,std::shared_ptr初始化案例如下,以及对应的refcount打印
    #include <iostream>
    #include <memory>
    
    struct C {int* data;};
    
    int main () {
      std::shared_ptr<int> p1;//默认构造函数,refcount为0
      std::shared_ptr<int> p2 (nullptr);//使用一个空的对象初始化时refcount也为0
      //普通指针初始化是引用计数为1,p3,p4
      std::shared_ptr<int> p3 (new int);
      std::shared_ptr<int> p4 (new int, std::default_delete<int>());
      
      //拥有allocator的时候初始化同样引用计数为1
      //但是紧接着又用改智能指针拷贝构造初始化其他智能指针p6,
      //所以最后其引用计数为2,p5
      std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
    	
      //这里p6本身为1,但是因为使用std::move去初始化p7,将p6指向转给了p7
      //则p6智能指针recount--变为0,p7 ++由1变为2
      std::shared_ptr<int> p6 (p5);
      std::shared_ptr<int> p7 (std::move(p6));
      std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
      std::shared_ptr<C> obj (new C);
      std::shared_ptr<int> p9 (obj, obj->data);
    
      std::cout << "use_count:\n";
      std::cout << "p1: " << p1.use_count() << '\n';
      std::cout << "p2: " << p2.use_count() << '\n';
      std::cout << "p3: " << p3.use_count() << '\n';
      std::cout << "p4: " << p4.use_count() << '\n';
      std::cout << "p5: " << p5.use_count() << '\n';
      std::cout << "p6: " << p6.use_count() << '\n';
      std::cout << "p7: " << p7.use_count() << '\n';
      std::cout << "p8: " << p8.use_count() << '\n';
      std::cout << "p9: " << p9.use_count() << '\n';
      return 0;
    }
    
    输出如下
    use_count:
    p1: 0
    p2: 0
    p3: 1
    p4: 1
    p5: 2
    p6: 0
    p7: 2
    p8: 1
    p9: 2
    
  • 析构函数
    // shared_ptr destructor example
    #include <iostream>
    #include <memory>
    
    int main () {
      auto deleter = [](int*p){
        std::cout << "[deleter called]\n"; delete p;
      };
      //使用特殊的delete函数去构造,析构的时候会执行改delete 中lamada表达式内容.即构造函数案例中的p5初始化方式
      std::shared_ptr<int> foo (new int,deleter);
      std::cout << "use_count: " << foo.use_count() << '\n';
    
      return 0;                        // [deleter called]
    }
    
    输出如下
    use_count: 1
    [deleter called]
    
  • =赋值运算符
    // shared_ptr::operator= example
    #include <iostream>
    #include <memory>
    
    int main () {
      std::shared_ptr<int> foo;
      std::shared_ptr<int> bar (new int(10));
      
      /*
      此时foo的引用计数为0,bar初始化后引用计数为1
      这里进行赋值操作,即foo的指向发生了变化,指向了bar
      1.foo引用计数--,因为已经为0了,此时直接释放foo原来的空间
      2.bar引用计数++变为2
      3.更改foo的引用计数和bar引用计数相等,并使得foo指向bar.因为他们共享同一个空间
      执行完之后fool和bar引用计数都相等,且解引用后数值都为0
      */
      foo = bar;                          // copy
      std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';
      std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';
      /*
      这里重新对bar进行了初始化,即原先的指向发生了更改,所以它的引用计数--,并且内容变为新的地址空间内容20
      foo继续指向原先空间,但是内容并未变化。同时原先地址因为bar并不引用了,所以foo的引用计数--
      */
      bar = std::make_shared<int> (20);   // move
      
      std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';
      std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';
    
      std::unique_ptr<int> unique (new int(30));
      foo = std::move(unique);            // move from unique_ptr
    
      std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';
      std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';
    
      return 0;
    }
    
    输出如下
    *foo: 10 foo.count 2
    *bar: 10 bar.count 2
    *foo: 10 foo.count 1
    *bar: 20 bar.count 1
    *foo: 30 foo.count 1
    *bar: 20 bar.count 1
    
  • shared_ptr::swap,交换两个shared_ptr地址空间内容,但并不破坏各自引用计数
    
    // shared_ptr::swap example
    #include <iostream>
    #include <memory>
    
    int main () {
      std::shared_ptr<int> foo (new int(10));
      std::shared_ptr<int> bar (new int(20));
      
      std::cout << "befor swap" << '\n';
      std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';
      std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';
    
      foo.swap(bar);
      
      std::cout << "after swap" << '\n';
      std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';
      std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';
    
      return 0;
    }
    
    输出如下
    befor swap
    *foo: 10 foo.count 1
    *bar: 20 bar.count 1
    after swap
    *foo: 20 foo.count 1
    *bar: 10 bar.count 1
    
  • shared_ptr::reset 替换所管理的对象
    // shared_ptr::reset example
    #include <iostream>
    #include <memory>
    
    int main () {
      std::shared_ptr<int> sp;  // empty
    
      sp.reset (new int);       // 替换所管理对象,让其更换地址指向
      std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';
      *sp=10;
      std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';
    
      sp.reset (new int);       // 清除上一个指针指向的内容,重新进行更换
      std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';
      *sp=20;
      std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';
    
      sp.reset();               // deletes managed object
      //std::cout << *sp << " " << sp.use_count() << '\n';
    
      return 0;
    }
    
    输出如下,可以看到reset之后的地址发生了变化,即更改了指针的指向
    0 1 0x434cd50
    10 1 0x434cd50
    0 1 0x434cd90
    20 1 0x434cd90
    
  • shared_ptr::get获取初始指针
    	// shared_ptr::get example
    #include <iostream>
    #include <memory>
    
    int main () {
      int* p = new int (10);
      std::shared_ptr<int> a (p);
      std::shared_ptr<int> b (new int(20));
    
      //此时a和p共享同一个地址空间,所以a和p的内容都为0,地址空间一样
      if (a.get()==p)
        std::cout << "a and p point to the same location " << a << " " << p << '\n';
        
      std::cout << *a.get() << "\n";
      std::cout << *a << "\n";
      std::cout << *p << "\n";
        
      //此时a将共享空间释放,重新更换指向b,但是*p为普通指针,并无法跟随a更换指,所以p的地址内容变为0
      a=b;
      std::cout << "a and p after copy " << a << " " << b << '\n';
      // three ways of accessing the same address:
      std::cout << *a.get() << "\n";
      std::cout << *a << "\n";
      std::cout << *p << "\n";
    
      return 0;
    }
    
    输出如下:
    a and p point to the same location 0x21cf2f0 0x21cf2f0
    10
    10
    10
    a and p after copy 0x21cf330 0x21cf330
    20
    20
    0
    

关于shared_ptr循环引用问题

循环引用是指两个shared_ptr初始化之后相互指向,在函数作用域结束之后由于两个指针都保持相互的指向,引用计数都为1,此时各自占用的内存空间无法释放,最终产生内存泄露
举例如下:

#include<iostream>  
#include<memory>  
 
using namespace std;  
 
class B;  
class A{  
    public:  
        shared_ptr<B> ptr_A;  
        ~A(){  
            cout << "refcount " << ptr_A.use_count() << '\n';
            cout<<"~A()"<<endl;  
        }  
};  
class B{  
    public:  
        //shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....  
        shared_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了  
        ~B(){  
            cout << "refcount " << ptr_B.use_count() << '\n';
            cout<<"~B()"<<endl;  
        }  
};  
int main(){  
    shared_ptr<A> a(new A);  
    shared_ptr<B> b(new B);  
    a->ptr_A=b;  
    b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了... 
    cout << a.use_count() << " "  << b.use_count()<< endl;
    return 0;  
}  

输出如下,可以看到释放的之前两个智能指针的引用计数都为2,析构的时候各自引用计数执行–到·1,最终无法释放

2 2

将classB中的shared_ptr更改为weak_ptr即可成功释放

#include<iostream>  
#include<memory>  
 
using namespace std;  
 
class B;  
class A{  
    public:  
        shared_ptr<B> ptr_A;  
        ~A(){  
            cout << "refcount " << ptr_A.use_count() << '\n';
            cout<<"~A()"<<endl;  
        }  
};  
class B{  
    public:  
        //shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....  
        weak_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了  
        ~B(){  
            cout << "refcount " << ptr_B.use_count() << '\n';
            cout<<"~B()"<<endl;  
        }  
};  
int main(){  
    shared_ptr<A> a(new A);  
    shared_ptr<B> b(new B);  
    a->ptr_A=b;  
    b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了...  
    return 0;  
}  

输出如下,调用析构函数之前引一个智能指针的引用计已经将为1,执行析构之后即为0

1 2
refcount 1
~A()
refcount 0
~B()

参考文档:
http://www.cplusplus.com/reference/memory/shared_ptr/
https://www.xuebuyuan.com/3190713.html

  • 1
    点赞
  • 0
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值