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

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

文章目录

      • shared_ptr描述
        • 声明
        • 作用
        • 原理实现
      • 函数使用
      • 关于shared_ptr循环引用问题

shared_ptr描述

声明

shared_ptr属于C++11特性中新加的一种智能指针,它的实现方式是模版类,头文件

template class shared_ptr

所以使用shared_ptr的声明方式即为

std::shared_ptr 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 
    #include struct C { int* data;};int main () { std::shared_ptr<int> p1;//默认构造函数,refcount为0std::shared_ptr<int> p2 (nullptr);//使用一个空的对象初始化时refcount也为0//普通指针初始化是引用计数为1,p3,p4std::shared_ptr<int> p3 (new int);std::shared_ptr<int> p4 (new int, std::default_delete<int>());//拥有allocator的时候初始化同样引用计数为1//但是紧接着又用改智能指针拷贝构造初始化其他智能指针p6,//所以最后其引用计数为2,p5std::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变为2std::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:
    ";std::cout << "p1: " << p1.use_count() << '
    ';std::cout << "p2: " << p2.use_count() << '
    ';std::cout << "p3: " << p3.use_count() << '
    ';std::cout << "p4: " << p4.use_count() << '
    ';std::cout << "p5: " << p5.use_count() << '
    ';std::cout << "p6: " << p6.use_count() << '
    ';std::cout << "p7: " << p7.use_count() << '
    ';std::cout << "p8: " << p8.use_count() << '
    ';std::cout << "p9: " << p9.use_count() << '
    ';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 
    #include int main () { auto deleter = [](int*p){ std::cout << "[deleter called]
    "; delete p;};//使用特殊的delete函数去构造,析构的时候会执行改delete 中lamada表达式内容.即构造函数案例中的p5初始化方式std::shared_ptr<int> foo (new int,deleter);std::cout << "use_count: " << foo.use_count() << '
    ';return 0;                        // [deleter called]
    }
    
    输出如下
    use_count: 1
    [deleter called]
    
  • =赋值运算符
    // shared_ptr::operator= example
    #include 
    #include int main () { std::shared_ptr<int> foo;std::shared_ptr<int> bar (new int(10));/*此时foo的引用计数为0,bar初始化后引用计数为1这里进行赋值操作,即foo的指向发生了变化,指向了bar1.foo引用计数--,因为已经为0了,此时直接释放foo原来的空间2.bar引用计数++变为23.更改foo的引用计数和bar引用计数相等,并使得foo指向bar.因为他们共享同一个空间执行完之后fool和bar引用计数都相等,且解引用后数值都为0*/foo = bar;                          // copystd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '
    ';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '
    ';/*这里重新对bar进行了初始化,即原先的指向发生了更改,所以它的引用计数--,并且内容变为新的地址空间内容20foo继续指向原先空间,但是内容并未变化。同时原先地址因为bar并不引用了,所以foo的引用计数--*/bar = std::make_shared<int> (20);   // movestd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '
    ';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '
    ';std::unique_ptr<int> unique (new int(30));foo = std::move(unique);            // move from unique_ptrstd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '
    ';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '
    ';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 
    #include int main () { std::shared_ptr<int> foo (new int(10));std::shared_ptr<int> bar (new int(20));std::cout << "befor swap" << '
    ';std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '
    ';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '
    ';foo.swap(bar);std::cout << "after swap" << '
    ';std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '
    ';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '
    ';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 
    #include int main () { std::shared_ptr<int> sp;  // emptysp.reset (new int);       // 替换所管理对象,让其更换地址指向std::cout << *sp << " " << sp.use_count() << " " << sp << '
    ';*sp=10;std::cout << *sp << " " << sp.use_count() << " " << sp << '
    ';sp.reset (new int);       // 清除上一个指针指向的内容,重新进行更换std::cout << *sp << " " << sp.use_count() << " " << sp << '
    ';*sp=20;std::cout << *sp << " " << sp.use_count() << " " << sp << '
    ';sp.reset();               // deletes managed object//std::cout << *sp << " " << sp.use_count() << '
    ';return 0;
    }
    
    输出如下,可以看到reset之后的地址发生了变化,即更改了指针的指向
    0 1 0x434cd50
    10 1 0x434cd50
    0 1 0x434cd90
    20 1 0x434cd90
    
  • shared_ptr::get获取初始指针
    	// shared_ptr::get example
    #include 
    #include 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 << '
    ';std::cout << *a.get() << "
    ";std::cout << *a << "
    ";std::cout << *p << "
    ";//此时a将共享空间释放,重新更换指向b,但是*p为普通指针,并无法跟随a更换指,所以p的地址内容变为0a=b;std::cout << "a and p after copy " << a << " " << b << '
    ';// three ways of accessing the same address:std::cout << *a.get() << "
    ";std::cout << *a << "
    ";std::cout << *p << "
    ";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  
#include  using namespace std;  class B;  
class A{   public:  shared_ptr<B> ptr_A;  ~A(){   cout << "refcount " << ptr_A.use_count() << '
';cout<<"~A()"<<endl;  }  
};  
class B{   public:  //shared_ptr ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....  shared_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了  ~B(){   cout << "refcount " << ptr_B.use_count() << '
';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  
#include  using namespace std;  class B;  
class A{   public:  shared_ptr<B> ptr_A;  ~A(){   cout << "refcount " << ptr_A.use_count() << '
';cout<<"~A()"<<endl;  }  
};  
class B{   public:  //shared_ptr ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....  weak_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了  ~B(){   cout << "refcount " << ptr_B.use_count() << '
';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

更多相关:

  • C++11标准中可以为模板定义别名,比如 template using ptr=std::shared_ptr; //这里模板定义ptr为智能指针std::shared_ptr定义的别名 所以实际应用中可以借此来简化代码,比如 #include #include

  • 先说结论,智能指针都是非线程安全的。 多线程调度智能指针 这里案例使用的是shared_ptr,其他的unique_ptr或者weak_ptr的结果都是类似的,如下多线程调度代码: #include #include #include #include #i...

  • 文章目录unique_ptr 智能指针的实现shared_ptr 智能指针的实现指针类型转换 unique_ptr 智能指针的实现 一个对象只能被单个unique_ptr 所拥有。 #include using namespace std;/*增加模板类型,保证智能指针的类型是由传入的类型决定的*/ t...

  • 文章目录weak_ptr描述声明作用原理实现函数成员使用总结 weak_ptr描述 声明 头文件: 模版类:template class weak_ptr 声明方式:std::weak_ptr statement 作用 根据boost库的官方描述,weak_ptr是...

  • enable_shared_from_this解析        enable_shared_from_this,是一个以其派生类为模板类型实参的基础模板,继承它,this指针就能变成shared_ptr。 什么时候该使用enable_shared_from_this模板类        在看下面的例子之前,简单说下使用背景,单有...

  • 经过长期探索,发现一个不需要手动设置线程休眠时间(e.g. std::this_thread::sleep_for(std::chrono::microseconds(1)))的代码: Github: https://github.com/log4cplus/ThreadPool #ifndef THREAD_POOL_H_7e...

  • nth_element(first,nth,last) first,last 第一个和最后一个迭代器,也可以直接用数组的位置。  nth,要定位的第nn 个元素,能对它进行随机访问. 将第n_thn_th 元素放到它该放的位置上,左边元素都小于它,右边元素都大于它. 测试代码: http://www.cplusplus.com...

  • c/c++老版本的rand()存在一定的问题,在转换rand随机数的范围,类型或者分布时,常常会引入非随机性。 定义在 中的随机数库通过一组协作类来解决这类问题:随机数引擎 和 随机数分布类 一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将(引擎和分布对象)定义为 st...

  • jsoncpp 是一个C++ 语言实现的json库,非常方便得支持C++得各种数据类型到json 以及 json到各种数据类型的转化。 一个json 类型的数据如下: {"code" : 10.01,"files" : "","msg" : "","uploadid" : "UP000000" } 这种数据类型方便我们人阅读以...

  • 问题如下: 已知一组数(其中有重复元素),求这组数可以组成的所有子集中,子 集中的各个元素和为整数target的子集,结果中无重复的子集。 例如: nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8 结果为: [[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]] 同样之前有...