智能指针

指针非常强大,是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();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 226,728评论 6 526
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,623评论 3 411
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,371评论 0 372
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,174评论 1 306
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,014评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,532评论 1 319
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,641评论 3 433
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,793评论 0 283
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,294评论 1 329
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,276评论 3 352
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,423评论 1 364
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,980评论 5 354
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,678评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,087评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,307评论 1 279
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,997评论 3 385
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,404评论 2 370

推荐阅读更多精彩内容

  • 导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针...
    7ee72f98ad17阅读 904评论 0 1
  • 学c++的人都知道,在c++里面有一个痛点,就是动态内存的管理,就我所经历的一些问题来看,很多莫名其妙的问题,最后...
    cpp加油站阅读 818评论 0 2
  • 12章之前的程序中使用的对象都有严格定义的生存期。 全局对象在程序启动时分配,在程序结束时销毁。 对于局部自动对象...
    Kreat阅读 439评论 0 0
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,569评论 28 53
  • 首先介绍下自己的背景: 我11年左右入市到现在,也差不多有4年时间,看过一些关于股票投资的书籍,对于巴菲特等股神的...
    瞎投资阅读 5,746评论 3 8