1. 程式人生 > 實用技巧 >Smart Pointers原始碼 + CppCon筆記Back to Basics: Smart Pointers

Smart Pointers原始碼 + CppCon筆記Back to Basics: Smart Pointers

智慧指標是基於RAII的理念設計的一個資源的封裝,能讓類不直接管理資源,從而減少錯誤發生(忘記釋放)。

1.unique_ptr

智慧指標的理解要和資源的所有權相聯絡。unique_ptr代表的是獨佔的所有權(exclusive ownership),所封裝的指標不能與其他共享,否則double free。

1.1.unique_ptr實現

unique_ptr主要的組成是一個所管理的指標,一個可以自定義的deleter, 而且move only(畢竟不能共享指標)。是一個處理low-level和非RAII的好方法。

原理:

unique_ptr的宣告

template <typename _Tp, typename _Tp_Deleter = default_delete<_Tp> > 
class unique_ptr;

unique_ptr中禁止拷貝

// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
template<typename _Up, typename _Up_Deleter> 
unique_ptr(const unique_ptr<_Up, _Up_Deleter>&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
template<typename _Up, typename _Up_Deleter> 
unique_ptr& operator=(const unique_ptr<_Up, _Up_Deleter>&) = delete;

deleter

template<typename _Tp> 
struct default_delete<_Tp>
{
    default_delete() {}

    template<typename _Up>
    default_delete(const default_delete<_Up>&) { }
    
    void operator()(_Tp* __ptr) const {
        static_assert(sizeof(_Tp)>0, "can't delete pointer to incomplete type");
        delete __ptr;
    } 
};

此外還有一套針對array的定義,這裡使用了template的specialization技法,輸入的是array的話會呼叫這個類。https://stackoverflow.com/questions/19923353/multiple-typename-arguments-in-c-template。

template<typename _Tp, typename _Tp_Deleter> 
class unique_ptr<_Tp[], _Tp_Deleter>

移動構造,release能獲得unique_ptr所管理指標,並釋放所有權

// Move constructors.
unique_ptr(unique_ptr&& __u) 
: _M_t(__u.release(), std::forward<deleter_type>(__u.get_deleter())) { }

__tuple_type _M -> typedef std::tuple<_Tp*, _Tp_Deleter> __tuple_type;

pointer get() const
{ return std::get<0>(_M_t); }

typename std::add_lvalue_reference<deleter_type>::type
get_deleter()
{ return std::get<1>(_M_t); }

pointer release() 
{
    pointer __p = get();
    std::get<0>(_M_t) = 0; // nullify 所管理指標
    return __p;
}

解構函式

~unique_ptr() { reset(); }

// 替換所管理的指標,如果沒有引數,就是與一個空指標替換
reset(pointer __p = pointer()) {
    if (__p != get()) {        // unique_ptr為空時不會釋放
         get_deleter()(get()); // 等同deleter(ptr), 使用deleter釋放ptr
         std::get<0>(_M_t) = __p;
    }
}

1.2.make_unique實現

unique_ptr dptr = make_unique (1);

forward + variadic template(並不是pack expression,所以c++14就有make_unique)

/// std::make_unique for single objects
template<typename _Tp, typename... _Args>
inline typename _MakeUniq<_Tp>::__single_object
make_unique(_Args&&... __args)
{ return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }

1.3.unique_ptr使用

所有權轉換的例子

// a function consuming a unique_ptr can take it by value or by rvalue reference
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}

int main() {
    auto p = std::make_unique<D>(); // p is a unique_ptr that owns a D
    auto q = pass_through(std::move(p)); 
    assert(!p); // now p owns nothing and holds a null pointer
    q->bar();   // and q owns the D object
}

1.3.1.自定義deleter

2.shared_ptr

一種管理可共享資源的智慧指標,這就是說有多個shared_ptr指向同一個指標,並存在一個計數,當計數為0時後才釋放資源。

2.0.shared_ptr和weak_ptr

解決成環問題所以引入了weak_ptr,只增加weak_count不增加use_count
https://stackoverflow.com/questions/4984381/shared-ptr-and-weak-ptr-differences

class A { shared_ptr<B> b; ... };
class B { shared_ptr<A> a; ... };
shared_ptr<A> x(new A);  // +1
x->b = new B;            
x->b->a = x;             // +1
// Ref count of 'x' is 2.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, there will be a memory leak:
// 2 is decremented to 1, and so both ref counts will be 1.
// (Memory is deallocated only when ref count drops to 0)

2.1.shared_ptr實現

原理

shared_ptr包含了資源指標和一個計數的內部類,在計數類__shared_count中,包含儲存引用計數,弱引用計數還有一個指向deleter和資源指標的_Sp_counted_base。_Sp_counted_ptr是對_Sp_counted_base的封裝。

2.1.1.shared_ptr和__shared_ptr

shared_ptr只是__shared_ptr的簡單封裝,而__shared_ptr包含了資源指標和一個計數類__shared_count。 __shared_count負責資源計數,釋放幷包含deleter。

// __shared_ptr的成員
element_type*           _M_ptr;         // Contained pointer.
__shared_count<_Lp>  _M_refcount;       // Reference counter.擁有deleter和資源指標

shared_ptr只是__shared_ptr的簡單封裝

// Construct a shared_ptr that owns the pointer __p.

template<typename _Tp1>
explicit shared_ptr(_Tp1* __p)
  : __shared_ptr<_Tp>(__p) { }

__shared_ptr包含資源的指標和一個棧上的計數物件__shared_count,__shared_ptr不負責_M_ptr的釋放,這些活是由__shared_count來負責的。

// __shared_ptr的宣告
template<typename _Tp, _Lock_policy _Lp>
    class __shared_ptr
    : public __shared_ptr_access<_Tp, _Lp> {/* 省略*/}

// __shared_ptr 的成員變數
element_type*           _M_ptr;         // Contained pointer.
__shared_count<_Lp>     _M_refcount;    // Reference counter.擁有deleter和資源指標

// __shared_ptr 的解構函式
~__shared_ptr() = default;

__shared_ptr的建構函式之一

template<typename _Yp, typename _Deleter, typename = _SafeConv<_Yp>>
__shared_ptr(_Yp* __p, _Deleter __d)
    : _M_ptr(__p), _M_refcount(__p, std::move(__d)) // _M_refcount擁有deleter和資源指標
{
  static_assert(__is_invocable<_Deleter&, _Yp*&>::value,
      "deleter expression d(p) is well-formed");
  _M_enable_shared_from_this_with(__p);
}

2.1.2.__shared_ptr_access

__shared_ptr的父類__shared_ptr_access主要負責過載解引用和->操作符。

using element_type = _Tp;
element_type&
operator*() const noexcept
{
    __glibcxx_assert(_M_get() != nullptr); // 會對是否為空指標進行判斷。
    return *_M_get();
}

2.1.3.__shared_count

__shared_count是shared_ptr的計數模組,也是真正管理釋放的部分(或者說_Sp_counted_base更準確),且多個shared_ptr例項共用一個__shared_count(__shared_count中賦值建構函式可以看到_Sp_counted_base<_Lp>* __tmp = __r._M_pi;)。不過切忌分別建立兩個獨立的shared_ptr指向同個資源,control block(計數模組)是不一樣的,還是會double delete。

成員變數

_Sp_counted_base<_Lp>*  _M_pi;

__shared_count的建構函式

template<_Lock_policy _Lp>
class __shared_count{/* 省略*/}

// _Sp_counted_base<_Lp>*  _M_pi;
template<typename _Ptr>
explicit __shared_count(_Ptr __p) : _M_pi(0) {
    __try {
        // _Sp_counted_base<_Lp>* _M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);
        _M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p); 
    }
    __catch(...) {
        delete __p;
        __throw_exception_again;
    }
}

__shared_count的解構函式

~__shared_count() noexcept
{
  if (_M_pi != nullptr)
    _M_pi->_M_release(); // use_count--, 如果use_count等於1,清理資源。
}

針對_M_pi_, 其可能是Sp_counted_deleter或者_Sp_counted_ptr的多型表現。

Sp_counted_deleter在_Sp_counted_ptr的基礎外多了deleter和allocator

// 自定義deleter時的__shared_count的建構函式
// ::new (__mem) _Sp_cd_type(__p, std::move(__d), std::move(__a)); // placement new, 在已分配的記憶體__mem上再分配
// _Sp_counted_base<_Lp>*  _M_pi = __mem;

template<typename _Ptr, typename _Deleter, typename _Alloc>
__shared_count(_Ptr __p, _Deleter __d, _Alloc __a) : _M_pi(0)
{
  typedef _Sp_counted_deleter<_Ptr, _Deleter, _Alloc, _Lp> _Sp_cd_type;
  __try
    {
      typename _Sp_cd_type::__allocator_type __a2(__a);
      auto __guard = std::__allocate_guarded(__a2);
      _Sp_cd_type* __mem = __guard.get();
      ::new (__mem) _Sp_cd_type(__p, std::move(__d), std::move(__a));
      _M_pi = __mem;
      __guard = nullptr;
    }
  __catch(...)
    {
      __d(__p); // Call _Deleter on __p.
      __throw_exception_again;
    }
}

2.1.4._Sp_counted_base

_Sp_counted_base的成員函式

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

_Sp_counted_base的建構函式和解構函式

template<_Lock_policy _Lp = __default_lock_policy>
    class _Sp_counted_base
    : public _Mutex_base<_Lp> {/* 省略*/}

// _Sp_counted_base的建構函式
_Sp_counted_base() noexcept
      : _M_use_count(1), _M_weak_count(1) { }

// _Sp_counted_base的解構函式
virtual
~_Sp_counted_base() noexcept
{ }

_Sp_counted_base的資源釋放函式

// Called when _M_use_count drops to zero, to release the resources
// managed by *this.
virtual void
_M_dispose() noexcept = 0; 

 void
_M_release() noexcept
{
  // 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) 
    {
      _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
      _M_dispose();
      // There must be a memory barrier between dispose() and destroy()
      // to ensure that the effects of dispose() are observed in the
      // thread that runs destroy().
      // See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
      if (_Mutex_base<_Lp>::_S_need_barriers)
        {
          __atomic_thread_fence (__ATOMIC_ACQ_REL);
        }
      // 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);
          _M_destroy();
        }
    }
}
// _Sp_counted_base喜加一
void
_M_add_ref_copy()
{ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }

// __shared_count的複製 -> 使用同一個_Sp_counted_base,並讓其喜加一
__shared_count(const __shared_count& __r) noexcept
: _M_pi(__r._M_pi)
{
  if (_M_pi != 0)
    _M_pi->_M_add_ref_copy();
}

_Sp_counted_ptr 和 _Sp_counted_deleter

// Counted ptr with no deleter or allocator support
  template<typename _Ptr, _Lock_policy _Lp>
    class _Sp_counted_ptr final : public _Sp_counted_base<_Lp> {/* 省略*/}

// _Sp_counted_ptr的釋放函式
virtual void
_M_dispose() noexcept
{ delete _M_ptr; }

virtual void
_M_destroy() noexcept
{ delete this; }

// Support for custom deleter and/or allocator
  template<typename _Ptr, typename _Deleter, typename _Alloc, _Lock_policy _Lp>
    class _Sp_counted_deleter final : public _Sp_counted_base<_Lp> {/* 省略*/}

_Sp_counted_deleter的釋放
// _Impl類是利用EBO的一種實現,沒看明白

~_Sp_counted_deleter() noexcept { }

virtual void
_M_dispose() noexcept
{ _M_impl._M_del()(_M_impl._M_ptr); }

virtual void
_M_destroy() noexcept
{
  __allocator_type __a(_M_impl._M_alloc());
  __allocated_ptr<__allocator_type> __guard_ptr{ __a, this };
  this->~_Sp_counted_deleter();
}

2.2.Aliasing constructor

// Aliasing constructor
     /**
 *  @brief  Constructs a %shared_ptr instance that stores @a __p
 *          and shares ownership with @a __r.
 *  @param  __r  A %shared_ptr.
 *  @param  __p  A pointer that will remain valid while @a *__r is valid.
 *  @post   get() == __p && use_count() == __r.use_count()
 *
 *  This can be used to construct a @c shared_ptr to a sub-object
 *  of an object managed by an existing @c shared_ptr.
 *
 * @code
 * shared_ptr< pair<int,int> > pii(new pair<int,int>());
 * shared_ptr<int> pi(pii, &pii->first);
 * assert(pii.use_count() == 2);
 * @endcode
 */
template<typename _Yp>
  shared_ptr(const shared_ptr<_Yp>& __r, element_type* __p) noexcept
  : __shared_ptr<_Tp>(__r, __p) { }

// template<typename _Tp, _Lock_policy _Lp = __default_lock_policy>  class __shared_ptr;

template<typename _Yp>
__shared_ptr(const __shared_ptr<_Yp, _Lp>& __r,
      element_type* __p) noexcept
      _M_ptr(__p), _M_refcount(__r._M_refcount) // never throws
}

一個shared_ptr的特殊用法,指向類的成員,並增加類的引用計數,來增加使用成員類時的生命週期

struct Bar { 
    // some data that we want to point to
};

struct Foo {
    Bar bar;
};

shared_ptr<Foo> f = make_shared<Foo>(some, args, here);
shared_ptr<Bar> specific_data(f, &f->bar);

// ref count of the object pointed to by f is 2
f.reset();

// the Foo still exists (ref cnt == 1)
// so our Bar pointer is still valid, and we can use it for stuff
some_func_that_takes_bar(specific_data);

2.3.進行使用make_shared

  • new和delete對應
  • cache locality

用同個malloc申請記憶體給control block(計數模組)和資源 -> 在相鄰地址

3.weak_ptr

weak_ptr不能解引用,所以可以理解成一個shared_ptr的門票,如果你由一個weak_ptr你也就能獲得相應的shared_ptr(使用門票來使用)。

3.1.weak_ptr使用

3.1.1.weak_ptr由shared_ptr獲得

shared_ptr<Thing> sp(new Thing);
weak_ptr<Thing> wp1(sp); // construct wp1 from a shared_ptr
weak_ptr<Thing> wp2; // an empty weak_ptr - points to nothing

wp2 = sp; // wp2 now points to the new Thing
weak_ptr<Thing> wp3 (wp2); // construct wp3 from a weak_ptr
weak_ptr<Thing> wp4
wp4 = wp2; // wp4 now points to the new Thing.

3.1.2.weak_ptr轉為shared_ptr

使用lock成員函式

shared_ptr<_Tp>
lock() const noexcept
{ return shared_ptr<_Tp>(*this, std::nothrow); } 

std::nothrow是一個用於區分過載的東西,區別是如果weak_ptr已經expired了,會丟擲異常。

shared_ptr有對應的建構函式(throwing)

// throwing版本
// Now that __weak_count is defined we can define this constructor:
template<_Lock_policy _Lp>
inline
__shared_count<_Lp>::__shared_count(const __weak_count<_Lp>& __r)
: _M_pi(__r._M_pi)
{
  if (_M_pi != nullptr)
    _M_pi->_M_add_ref_lock();
  else
    __throw_bad_weak_ptr(); // 如果weak_ptr已經expired了,丟擲一個bad weak ptr異常
}

// no throwing版本
// Now that __weak_count is defined we can define this constructor:
template<_Lock_policy _Lp>
inline
__shared_count<_Lp>::
__shared_count(const __weak_count<_Lp>& __r, std::nothrow_t)
   : _M_pi(__r._M_pi)
{
  if (_M_pi != nullptr)
    if (!_M_pi->_M_add_ref_lock_nothrow())
      _M_pi = nullptr;
}

推薦使用lock,再判讀是否是空指標(是否expired)

3.1.3.expired和use_count

判斷weak_ptr所指物件是否expired,和多少shared_ptr物件指向被管理的資源物件。

bool expired() const noexcept
{ return _M_refcount._M_get_use_count() == 0; }

long use_count() const noexcept
{ return _M_refcount._M_get_use_count(); }

3.1.3.std::enable_shared_from_this

還有一個問題沒有解決,當一個物件T建立後,如果一個函式需要shared_ptr的形參, 我們該如何解決?顯然用shared_ptr是不合理的,因為這樣離開函式體時候物件T就會被釋放,很容易引起問題。此時我們需要有個辦法,讓物件T自己能拿出一個shared_ptr,且shared_ptr的control block都相同,不會使用完函式時候把T釋放。這時候就可以使用基於CRTP技法的std::enable_shared_from_this,讓物件自己擁有一張門票(weak_ptr),每次只需要share_from_this()換成shared_ptr就好了。(weak_from_this() is in C++17)

同樣,這也是一種讓函式介入物件生命週期的方法。


/**
*  @brief Base class allowing use of member function shared_from_this.
*/
template<typename _Tp>
class enable_shared_from_this {
      /*省略*/
public:
      mutable weak_ptr<_Tp>  _M_weak_this;

      shared_ptr<_Tp> shared_from_this()
      { return shared_ptr<_Tp>(this->_M_weak_this); }
}