美文网首页
智能指针

智能指针

作者: 某昆 | 来源:发表于2020-11-30 20:53 被阅读0次

指针非常强大,是c++的精髓所在,但用裸指针总有点心惊肉跳,怕一个不小心就引起内存问题,排查起来就相当费时费力了。裸指针有哪些问题:

  • 忘记释放资源,导致资源泄露(常发生内存泄漏问题)
  • 同一资源释放多次,导致释放野指针,程序崩溃
  • 已写了释放资源的代码,但是由于程序逻辑不满足条件,导致释放- 资源的代码未被执行到
  • 代码运行过程中发生异常,导致释放资源的代码未被执行到
    智能指针就是用来解决这些问题的,它能让开发者不关心资源的释放,因为智能指针可以自动完成资源的释放,它能保证无论代码怎么跑,资源最终都会释放

智能指针本质上是一个泛型类,类中包含传入的指针,当开发者初始化一个智能指针时,此时智能指针是在栈上初始化,如果智能指针一旦出了作用域,它就会被回收,执行智能指针的析构函数,智能指针则可趁机决定是否释放内部的指针资源。

智能指针的基本原理,就是“栈上的对象出作用域会自动析构”,排出萝卜带出泥,自己被析构了,顺便把真正的指针给释放了

一、自定义智能指针

如果让我们自己来实现一个智能指针,我们该怎么实现呢?

  • 智能回收,如果智能指针被回收,需要判断,真正的指针是否要被回收
  • 重定义操作符,*号以及 → 等,智能指针和裸指针的使用体验相同
  • 指针计数,如果有多个指针指向同一个对象,应该通过计数解决指针,因为一个智能指针到生命周期了,但此对象还被其它智能指针引用着,所以还不能回收对象

按照这几个要求,我们写一个相当简单的智能指针

template<typename T>
class smart_ptr{
private:
int* m_count;
T* m_ptr;
public:
smart_ptr():m_ptr(nullptr), m_count(nullptr){};
smart_ptr(T* ptr):m_ptr(ptr){
    m_count = new int(1);
};
~smart_ptr() {
    (*m_count)--;
    cout << "smart ptr delete count = " << *m_count << endl;
    if ((*m_count) == 0) {
        delete m_ptr;
        delete m_count;
    }
};
smart_ptr(smart_ptr& ptr): m_ptr(ptr.m_ptr), m_count(ptr.m_count) {
    (*m_count)++;
}
smart_ptr& operator=(smart_ptr& ptr){
    m_ptr = ptr.m_ptr;
    m_count = ptr.m_count;
    (*m_count)++;
    return *this;
}
int getCount(){return (*m_count);};
T& operator*(){
    return *m_ptr;
}
T* operator->(){
    return m_ptr;
}
};
  void test_smartptr(){
{
    smart_ptr<stu> ptr(new stu);
    smart_ptr<stu> ptr2(ptr);
    smart_ptr<stu> ptr3;
    ptr3 = ptr2;
    ptr->name_ptr = "tom";
    cout << ptr->name_ptr << " count = " << ptr.getCount() << "  ptr.count = " << ptr.getCount() << "  ptr3.count = " << ptr3.getCount() << endl;
}
}

执行对应测试方法,log如下:

tom count = 3  ptr.count = 3  ptr3.count = 3
smart ptr delete count = 2
smart ptr delete count = 1
smart ptr delete count = 0
 delete stu

上面的代码已经初步实现一个智能指针,当两个智能指针指向同一个对象时,ptr收回时,因为count值为1,说明外边还有一个智能指针在引用此对象,因此不能回收,等到ptr2回收时,count为0了,才回收相应对象。但上面的示例代码还是比较简单,因为没有考虑多线程情况,在源码中是通过cas操作来实现线程安全的。

另外此处还有一个小细节,m_count为什么是一个指针?如果m_count只是一个int值,那么在执行复制构造函数时,只能更改自身的m_count值,其它智能指针的m_count值无法更改或者改得比较麻烦。因为用其它智能指针赋值生成一个新的智能指针时,新旧两个智能指针的m_count值都应该加1,所以,用指针就方便多了,新旧两个智能指针的m_count指向同一块内存区域,这样,改了一处,另一处也就更改了。

比对自定义智能指针的相关代码,我们先来看看shared_ptr和weak_ptr的用法

二、智能指针的用法

shared_ptr和weak_ptr,都是带引用计数的智能指针。同之前的自定义智能指针一样,当允许多个智能指针指向同一个资源的时候,每一个智能指针都会给资源的引用计数加1,当一个智能指针析构时,同样会使资源的引用计数减1,这样最后一个智能指针把资源的引用计数从1减到0时,就说明该资源可以释放了。

shared_ptr,强智能指针,可以多个shared_ptr指向同一个资源,也是使用得最普遍的智能指针,但它有个问题,它不能解决循环引用问题。

  class B;
  class A{
public:
A(){cout << "create a" << endl;}
~A(){cout << "destroy a" << endl;}
shared_ptr<B> _ptrb;
};
  class B{
public:
B(){cout << "create b" << endl;}
~B(){cout << "destroy a" << endl;}
shared_ptr<A> _ptra;
};
void test_loop_refrence(){
shared_ptr<A> ptra(new A);
shared_ptr<B> ptrb(new B);
ptra->_ptrb = ptrb;
ptrb->_ptra = ptra;
cout << ptra.use_count() << endl;
cout << ptrb.use_count() << endl;
}
日志输出:
create a
create a
2
2

循环引用下,出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和 B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是 A和B的引用计数为0),因此造成两个new出来的A和B对象无法释放, 导致内存泄露,这个问题就是“强智能指针的交叉引用(循环引用)问题”。weak_ptr则可以解决这种问题,将A 和 B 类中的智能指针改为weak_ptr,即可解决上述问题

弱智能指针weak_ptr区别于shared_ptr之处在于:

  • weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在
  • weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
  • weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock方法提升为shared_ptr强智能指针,才能访问资源

一般来说,使用智能指针可以使用以下原则:定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr。

三、源码分析

这里分析的源码来自于gcc-10.2.0,gcc-10.2.0/libstdc++-v3/include/tr1/shared_ptr.h
shared_ptr和weak_ptr牵涉有好几个不同的类,先来看看它们牵涉哪些类以及相应的关系:

shared_ptr继承__shared_ptr,而__shared_ptr中有两个成员变量:

  • _Tp*,真正的指针,指向要操作的数据
  • __shared_count,用于计数相关的逻辑

weak_ptr也是同样的结构。双方的count成员变量都引用着一个_Sp_counted_base指针,所以,先来看看_Sp_counted_base

template<_Lock_policy _Lp = __default_lock_policy>
 class _Sp_counted_base
 : public _Mutex_base<_Lp>
 {
 public: 
 _Sp_counted_base()
 : _M_use_count(1), _M_weak_count(1) { }
  
 virtual
 ~_Sp_counted_base() // nothrow
 { }

 // Called when _M_use_count drops to zero, to release the resources
 // managed by *this.
 virtual void
 _M_dispose() = 0; // 当use count为0时,释放真实指针
  
 // Called when _M_weak_count drops to zero.
 virtual void
 _M_destroy() // 当weak count为0时,销毁自己
 { delete this; }
  
 virtual void*
 _M_get_deleter(const std::type_info&) = 0;

 void
 _M_add_ref_copy()
 { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); } //增加use count

 void
 _M_add_ref_lock(); //从weak_ptr变成shared_ptr时需要调用的方法
  
 void
 _M_release() // nothrow
 {
   // Be race-detector-friendly.  For more info see bits/c++config.
   _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
 if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1){
   _M_dispose();
   if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1){
       _M_destroy();
   }
 }
 }

 void
 _M_weak_add_ref() // nothrow 增加weak count
 { __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }

 void
 _M_weak_release() // nothrow
 {
   // Be race-detector-friendly. For more info see bits/c++config.
   _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
 if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
 {
       _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
   if (_Mutex_base<_Lp>::_S_need_barriers)
     {
       // See _M_release(),
       // destroy() must observe results of dispose()
   __atomic_thread_fence (__ATOMIC_ACQ_REL);
     }
   _M_destroy();
 }
 }

 long
 _M_get_use_count() const // nothrow 返回use count数
 {
   return const_cast<const volatile _Atomic_word&>(_M_use_count);
 }

 private: 
 _Sp_counted_base(_Sp_counted_base const&);
 _Sp_counted_base& operator=(_Sp_counted_base const&);

 _Atomic_word  _M_use_count;     // #shared
 _Atomic_word  _M_weak_count;    // #weak + (#shared != 0)
 };

_M_use_count,代表着有多少个shared_ptr指向了引用数据,而_M_weak_count则代表了weak_ptr的个数。

当_M_release方法时,如果_M_use_count等于1,自减之后等于0,则表示没有shared_ptr再指向相应资源了,则要回收掉相应的资源,即那个管理的真实的指针。如果_M_weak_count自减之后等于0,则需要调用_M_destroy方法,销毁自己。

virtual void
_M_dispose() // nothrow
{ _M_del(_M_ptr); }

_M_dispose方法的实现在_Sp_counted_base_impl 中,删除对应指针

接下来一起看看__shared_count类的源码(有删减,将一些重点突出)

template<_Lock_policy _Lp = __default_lock_policy>
class __shared_count
{
public:
  __shared_count()
  : _M_pi(0) // nothrow
  { }
//析构函数,执行_Sp_counted_base的release方法,use_count自减,判断是否要删除管理的指针
//weak_count自减,判断是否要删除 _Sp_counted_base 自身的指针,而 _Sp_counted_base 也是以指针形式保存在 __shared_count中
  ~__shared_count() // nothrow
  {
if (_M_pi != 0)
  _M_pi->_M_release();
  }
  //复制构造函数,执行_M_add_ref_copy方法,自己以及被复制的对象,use_count都会自增一
  __shared_count(const __shared_count& __r)
  : _M_pi(__r._M_pi) // nothrow
  {
if (_M_pi != 0)
  _M_pi->_M_add_ref_copy();
  }
  long
  _M_get_use_count() const // nothrow
  { return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }

  _Sp_counted_base<_Lp>*  _M_pi;
};

从源码中可以看出,当执行复制构造函数时,会执行_Sp_counted_base的_M_add_ref_copy方法,use_count将会自增。当执行析构函数时,将会执行_Sp_counted_base的release方法,而release方法中将会检查use_count和weak_count,删除管理的指针或_Sp_counted_base自身。_Sp_counted_base正好也是以指针形式存在于__shared_count中,执行_Sp_counted_base的destroy方法时,_Sp_counted_base的裸指针将被删除,不会有泄漏。

整个思路和前文第一部分的自定义智能指针一模一样

接下来我们再看看__weak_count的源码

template<_Lock_policy _Lp>
class __weak_count
{
public:
  __weak_count()
  : _M_pi(0) // nothrow
  { }
//复制构建函数,只是调用_M_weak_add_ref,自增weak_count
  __weak_count(const __shared_count<_Lp>& __r)
  : _M_pi(__r._M_pi) // nothrow
  {
if (_M_pi != 0)
  _M_pi->_M_weak_add_ref();
  }
  //复制构建函数,只是调用_M_weak_add_ref,自增weak_count
  __weak_count(const __weak_count<_Lp>& __r)
  : _M_pi(__r._M_pi) // nothrow
  {
if (_M_pi != 0)
  _M_pi->_M_weak_add_ref();
  }
   
  ~__weak_count() // nothrow
  {
if (_M_pi != 0)
  _M_pi->_M_weak_release();
  }

  long
  _M_get_use_count() const // nothrow
  { return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }

  _Sp_counted_base<_Lp>*  _M_pi;
};

与__shared_count不同的是,执行复制构造函数时,只是自增weak_count的值。执行析构函数时,执行_Sp_counted_base的_M_weak_release方法,_M_weak_release方法会判断weak_count数量,决定是否释放_Sp_counted_base的指针,__weak_count的析构函数并不会释放管理的真正的指针。

接下来看看__shared_ptr类

 //使用__weak_count作参数的复制构造函数,意味着此智能指针要转化为shared_ptr,不再是weak_ptr
//所以,需要调用_M_add_ref_lock方法,自增use_count
template<_Lock_policy _Lp>
inline
__shared_count<_Lp>::
__shared_count(const __weak_count<_Lp>& __r)
: _M_pi(__r._M_pi)
{
  if (_M_pi != 0)
_M_pi->_M_add_ref_lock();
else
__throw_bad_weak_ptr();
}

template<typename _Tp, _Lock_policy _Lp>
class __shared_ptr
{
public:
typedef _Tp   element_type;
//默认构造函数
__shared_ptr()
: _M_ptr(0), _M_refcount() // never throws
{ }

//使用__shared_ptr作为参数的复制构造函数
template<typename _Tp1>
  __shared_ptr(const __shared_ptr<_Tp1, _Lp>& __r, __static_cast_tag)
: _M_ptr(static_cast<element_type*>(__r._M_ptr)),
_M_refcount(__r._M_refcount)
  { }
//使用__weak_ptr作为参数的复制构造函数,__shared_ptr的成员变量_M_refcount是__shared_count
//而__r._M_refcount是__weak_count,__shared_count的这类复制构造函数前最前面,它将会调用_M_add_ref_lock方法,自增use_count
  template<typename _Tp1>
  explicit
  __shared_ptr(const __weak_ptr<_Tp1, _Lp>& __r)
: _M_refcount(__r._M_refcount) // may throw
  {
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
// It is now safe to copy __r._M_ptr, as _M_refcount(__r._M_refcount)
// did not throw.
_M_ptr = __r._M_ptr;
}
//模拟指针使用方法而重写的运算符函数
operator*() const // never throws
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}

_Tp*
operator->() const // never throws
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}

_Tp*
get() const // never throws
{ return _M_ptr; }

// Implicit conversion to "bool"
public:
long
use_count() const // never throws
{ return _M_refcount._M_get_use_count(); }

_Tp*             _M_ptr;         // Contained pointer.
__shared_count<_Lp>  _M_refcount;    // Reference counter.
};

注意__shared_ptr的几个复制构造函数,它可以由__shared_ptr复制,也可以由__weak_ptr构造,当由__weak_ptr构造时,执行_M_add_ref_lock方法,其实是将weak_ptr转换成了shared_ptr,同时自增use_cont

最后,一起看看__weak_ptr的代码

template<typename _Tp, _Lock_policy _Lp>
class __weak_ptr
{
public:
  typedef _Tp element_type;
   
  __weak_ptr()
  : _M_ptr(0), _M_refcount() // never throws
  { }
  //weak_ptr并不能直接获取管理的指针,需要通过调用lock方法,转成shared_ptr,才能获取管理的指针并且完成赋值
  //而_M_refcount,根据weak_count的源码说明,只是调用_M_weak_add_ref,自增weak count
  template<typename _Tp1>
    __weak_ptr(const __weak_ptr<_Tp1, _Lp>& __r)
: _M_refcount(__r._M_refcount) // never throws
    {
  _M_ptr = __r.lock().get();
}
  template<typename _Tp1>
    __weak_ptr(const __shared_ptr<_Tp1, _Lp>& __r)
: _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
    { __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>) }
   
  //lock方法,将weak_ptr转换成一个shared_ptr,调用shared_ptr的一个复制构造函数
  __shared_ptr<_Tp, _Lp>
  lock() const // never throws
  {
__try
  {
    return __shared_ptr<element_type, _Lp>(*this);
  }
__catch(const bad_weak_ptr&)
  {
    return __shared_ptr<element_type, _Lp>();
  }
  } // XXX MT

  long
  use_count() const // never throws
  { return _M_refcount._M_get_use_count(); }
private:
  _Tp*           _M_ptr;         // Contained pointer.
  __weak_count<_Lp>  _M_refcount;    // Reference counter.
};

与__shared_ptr不同的是,__weak_ptr的复制构造函数只会自增weak count,不会自增use count,所以完全不会影响管理的指针的释放。

综上所述,__shared_ptr拥有成员变量__shared_count,而__shared_count拥有成员变量,准确说是一个指针,_Sp_counted_base*,_Sp_counted_base内有两个成员变量_M_use_count和_M_weak_count,当初始化__shared_ptr时,_M_use_count自增,用其它shared_ptr来初始化一个新的shared_ptr时,则二者的_M_use_count都会加1,最终在栈内,shared_ptr析构时,会计算当前_M_use_count是否为0,如果为0,则释放管理的指针,如果_M_weak_count也为0,则将内部的成员变量指针_Sp_counted_base释放。

__weak_ptr,和上述类似,只是它在初始化时是_M_weak_count自增,完全不影响_M_use_count,它析构时,依然会调用__weak_count的析构函数,即调用_Sp_counted_base的_M_weak_release方法,此方法只会判断weak_count是否为0,如果是0,则删除_Sp_counted_base指针,根本不会影响管理的真实指针。__weak_ptr通过调用lock方法可转换成__shared_ptr,其实也就是调用__shared_ptr的复制构造函数而已,不过use count会自增

通过这么多的讲解,weak_ptr为什么能解决双循环引用的问题呢?原因还是在于weak_count的设计,不会增加use count,所以不会干扰管理的指针回收。

而shared_ptr为什么能自动回收管理的指针呢,通过栈自动回收超出作用域的对象,回收shared_ptr时,根据use count决定是否回收管理的指针。

最后,貌似讲了半天,也没提是如何删除管理的真正的指针。_Sp_counted_base_impl继承_Sp_counted_base,它多了两个成员变量,_M_ptr和_M_del。其实在__shared_ptr初始化时,则会去初始化__shared_count,再去初始化_Sp_counted_base_impl,__shared_ptr内管理的指针会传递给_M_ptr,而_M_del是一个负责删除指针的结构体,所以在__shared_ptr析构时,会执行_shared_count的析构,而_shared_count析构,则会执行_Sp_counted_base_impl的_M_release方法,_M_release方法中会调用_M_dispose,回收管理的指针

除了shared_ptr和weak_ptr之外,还有一个智能指针,unique_ptr,顾名思义,它就是一个原生指针独一无二的拥有者和管理者,它不允许别的unique_ptr再占用原生指针,甚至它的复制构造函数以及赋值函数都是不允许调用的。

unique_ptr(const unique_ptr&) = delete;
  unique_ptr& operator=(const unique_ptr&) = delete;

unique_ptr(unique_ptr&&) = default;
unique_ptr& operator=(unique_ptr&&) = default;

用法:
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
  std::unique_ptr<Task>taskPtr5(new Task(55));

要用它,只能通过右值赋值或者直接传原生指针才行。

unique_ptr,它的原理就是通过右值赋值,实现一人独占,因为它是一人独占,所以根本不用计数了,unique_ptr自己的生命同期到了,管理的原生指针也会跟着回收了

它的用法较其它更简单一些,在此不多做介绍,以后再讲右值的时候再讲

四、enable_shared_from_this分析
智能指针有一个坑存在。

stu* stu_ptr = new stu("seven");
shared_ptr<stu> ptr1(stu_ptr);
shared_ptr<stu> ptr2(stu_ptr);
cout << " count1 = " << ptr1.use_count() << endl;
cout << " count2 = " << ptr2.use_count() << endl;
输出的log:
count1 = 1
 count2 = 1
 delete stu
 delete stu

明明ptr1 和 ptr2都是管理着stu_ptr,但它们的use_count方法返回值分别为1,而不是2,导致stu_ptr将会被回收两次,程序报错。

智能指针也不智能的原因在于shared_ptr的构造方法,如果不是调用复制构造函数,而是传入被管理的指针,那么对应的_M_refcount将会执行默认初始化方法,从前文可知,执行默认的初始化方法,那么use count将为1

template<typename _Tp1>
    explicit
    __shared_ptr(_Tp1* __p)
: _M_ptr(__p), _M_refcount(__p)
    {
  __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
  typedef int _IsComplete[sizeof(_Tp1)];
  __enable_shared_from_this_helper(_M_refcount, __p, __p);
}

所以,想要让指向同一指针的智能指针计数正常,第二个智能指针只能用复制构造函数,通过其它智能指针赋值才行。

回到 enable_shared_from_this,它的主要作用是提供一个函数,返回当前对象的一个shared_ptr。如果按照正常思路,得这么写:

shared_ptr<stu> getSharePtr(){
return shared_ptr<stu>(this);
}

但这样写正好踩了前面的坑,导致use_count为1,这个对象会被回收两次,肯定是不行的。从前面可知,如果要返回一个正常的智能指针,必须用其它智能指针来赋值。enable_shared_from_this就是用来解决这个问题的。

回看前面__shared_ptr的构建函数,它还调用了__enable_shared_from_this_helper方法,这个方法是干啥的呢?

template<typename _Tp1>
    friend void
    __enable_shared_from_this_helper(const __shared_count<_Lp>& __pn,
                 const __enable_shared_from_this* __pe,
                 const _Tp1* __px)
    {
  if (__pe != 0)
    __pe->_M_weak_assign(const_cast<_Tp1*>(__px), __pn);
}

如果某个对象继承__enable_shared_from_this,在构建__shared_ptr时,传入自身类型的指针,其实也可以看作是传入了__enable_shared_from_this指针,因为继承自__enable_shared_from_this,可以转换成这种指针,然后调用_M_weak_assign方法

template<typename _Tp1>
    void
    _M_weak_assign(_Tp1* __p, const __shared_count<_Lp>& __n) const
    { _M_weak_this._M_assign(__p, __n); }

mutable __weak_ptr<_Tp, _Lp>  _M_weak_this;

private:
  // Used by __enable_shared_from_this.
  void
  _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount)
  {
_M_ptr = __ptr;
_M_refcount = __refcount;
  }

__shared_ptr<_Tp, _Lp>
  shared_from_this()
  { return __shared_ptr<_Tp, _Lp>(this->_M_weak_this); }    

__enable_shared_from_this中有个弱智能指针成员变量,_M_weak_this,调用__weak_ptr的_M_assign方法,其实就是初始化__weak_ptr两个成员变量,生成一个非空的__weak_ptr。

继承__enable_shared_from_this的对象,想要获取指向自身的__shared_ptr,调用shared_from_this方法即可,将一个弱智能指针_M_weak_this转换成一个强智能指针,目的就实现了。

五、shared_ptr的线程安全

智能指针的线程安全问题,与一个朴素的流程问题相关:把大象关冰箱分成几步,三步

那么,生成一个shared_ptr分成几步,两步:

  • 引用计数
  • 指针赋值

引用计数是线程安全的,毋庸置疑,因为引用计数采用了cas(compare and set)的原子操作。

_Atomic_word  _M_use_count;

void
 _M_weak_add_ref() // nothrow
{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }

 void
 _M_release() // nothrow
  {
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
  {
        _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
    _M_dispose();
    if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
          {
        _M_destroy();
          }
  }
  }

锁可以分成乐观锁和悲观锁,悲观锁即是一般意义上的加锁,互斥同步,不管干什么,先加锁保护起来,然后操作。但加锁会导致代码运行效率变低,因为涉及到线程切换等各种事情。悲观锁就是使用互斥同步的手段来保证线程安全的

乐观锁,和悲观锁相反,不用互斥同步,但它依赖于硬件,因为我们需要操作和冲突检测这两个步骤具备原子性,如果不考虑互斥来实现,那只能使用硬件来完成了,硬件保证一个从主观上看起来需要多次操作的行为只通过一条处理器指令就能完成。

cas就是一种乐观锁,通过原子操作来实现多线程安全地写数据

指针赋值是线程安全的吗?明显不是的,源码中没有看到任何一处与指针赋值有关的线程安全代码。所以,shared_ptr赋值操作有两个步骤,但有一个步骤是不安全的,那么shared_ptr就是不安全的了。哪些操作是不安全的呢?可以参考 https://www.boost.org/doc/libs/1_73_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr_thread_safety 的几个示例,如果是多个线程读shared_ptr,那肯定是安全的,但如果是多个线程写 shared_ptr,那就不安全了。

关于线程不安全的问题,用图来说明就会更好理解了:

所以,遇到这种多线程写指针的情况,还是老老实实地加锁干活吧。值得一提的是,某些特殊的场景,可以灵活使用weak_ptr来做探测shared_ptr是否已经被回收了,不用加锁而解决部分的多线程问题。

因为weak_ptr的lock方法是通过检测 use_count值来判断shared_ptr是否已经被回收,如果没有被回收,则生成正确的shared_ptr,如果已回收,则生成一个空的shared_ptr,所以可以灵活使用weak_ptr,它可以有效地探测shared_ptr是否还存在,从而解决部分多线程问题。

__shared_ptr<_Tp, _Lp>
  lock() const // never throws
  {
return expired() ? __shared_ptr<element_type, _Lp>()
                 : __shared_ptr<element_type, _Lp>(*this);
  } // XXX MT

  bool
  expired() const // never throws
  { return _M_refcount._M_get_use_count() == 0; }

示例:

class Test{
private:
int* volatile m_ptr;
public:
Test() : m_ptr(new int(20)){
    cout << "create test" << endl;
}
~Test(){
    delete m_ptr;
    m_ptr = nullptr;
    cout << "delete test" << endl;
}
void show(){
    cout << *m_ptr << endl;
}
};

void threadSafe(weak_ptr<Test> pw) {
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<Test> ps = pw.lock();
if(ps != nullptr) {
    ps->show();
}
}

void test_safy_smartptr(){
shared_ptr<Test> p(new Test);
std::thread t1(threadSafe, weak_ptr<Test>(p));
t1.join();
//    t1.detach();
}

相关文章

  • 目录

    智能指针(1) 智能指针(2) 智能指针(3) 智能指针之使用 容器 - vector(1) 容器 - vecto...

  • 智能指针到Android引用计数

    智能指针 LightRefBase RefBaseStrongPointerWeakPointer 智能指针 这是...

  • C++面试重点再梳理

    智能指针 请讲一下智能指针原理,并实现一个简单的智能指针 智能指针其实不是一个指针。它是一个用来帮助我们管理指针的...

  • C++研发工程师笔试题/面试题(1-10)

    1. (1) 简述智能指针的原理;(2)c++中常用的智能指针有哪些?(3)实现一个简单的智能指针。 简述智能指针...

  • 第十六章 string类和标准模板库(2)智能指针模板类

    (二)智能指针模板类 智能指针是行为类似指针的类对象,但这种对象还有其他便于管理内存的功能。 1.使用智能指针 (...

  • Rust for cpp devs - 智能指针

    与 cpp 类似,Rust 也有智能指针。Rust 中的智能指针与引用最大的不同是,智能指针 own 内存,而引用...

  • C++ 引用计数技术及智能指针的简单实现

    1.智能指针是什么 简单来说,智能指针是一个类,它对普通指针进行封装,使智能指针类对象具有普通指针类型一样的操作。...

  • 智能指针

    1. 什么是智能指针? 智能指针是行为类似于指针的类对象,但这种对象还有其他功能。 2. 为什么设计智能指针? 引...

  • chrome中智能指针使用

    chrom中智能指针的概述和作用 chrome中智能指针的用法和总结 包含如下四种智能指针:scoped_ptr ...

  • c++智能指针用法

    智能指针是什么 智能指针是c++中有四个智能指针:auto_ptr、shared_ptr、weak_ptr、uni...

网友评论

      本文标题:智能指针

      本文链接:https://www.haomeiwen.com/subject/grudwktx.html