您的位置:首页 > 其它

shared_ptr Analysis

2015-09-24 07:33 309 查看
 



Smart pointer uses RAII(Resource Acquisition is Initialization) idiom to manage resource.  The C++ 11 standards accept it as first class library member, and it resides in namespace std, instead
of namaspace tr1. In fact, the _M_pi member is a _Sp_counted_ptr pointer actually, but is does not bring impact to our understading.
 
To use it, include header file <memory> in your source code file. Here is an example code slice:
  1 #include <memory>
  2 #include <stdio.h>
  3
  4 class Foo{
  5 public:
  6   Foo() {
  7     printf("Foo...\n");
  8   }
  9   ~Foo() {
 10     printf("~Foo...\n");
 11   }
 12 };
 13
 14 int main() {
 15   std::shared_ptr< Foo > sp1(new Foo);
 16   {
 17   std::shared_ptr< Foo > sp2 = sp1;
 18
 19   printf("SP1\'s use count = %ld\n", sp1.use_count());
 20   printf("SP2\'s use count = %ld\n", sp2.use_count());
 21   }
 22
 23   printf("SP1\'s use count = %ld\n", sp1.use_count());
 24
 25   sp1.reset();
 26
 27   return 0;
 28 }

 
Its output should be like this:
Foo...
SP1's use count = 2
SP2's use count = 2
SP1's use count = 1
~Foo...
 
Let us use gdb to view the f1's construction call stack:
#0  std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_Sp_counted_base() () at /usr/local/include/c++/4.9.0/bits/shared_ptr_base.h:112
#1  0x000000000040115a in std::_Sp_counted_ptr<Foo*, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr(Foo*) () at /usr/local/include/c++/4.9.0/bits/shared_ptr_base.h:369
#2  0x0000000000400fde in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<Foo*>(Foo*) () at /usr/local/include/c++/4.9.0/bits/shared_ptr_base.h:569
#3  0x0000000000400e76 in std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<Foo>(Foo*) () at /usr/local/include/c++/4.9.0/bits/shared_ptr_base.h:871
#4  0x0000000000400d43 in std::shared_ptr<Foo>::shared_ptr<Foo>(Foo*) () at /usr/local/include/c++/4.9.0/bits/shared_ptr.h:113
#5  0x0000000000400b73 in main () at test.cpp:17
 
When the f1 is being constructed(Line 17), its base class and base class members are constructed firstly:
Create a Foo object in heap(Foo*);
Call __shared_ptr()(shared_ptr_base.h:871);

Construct __shared_ptr's _M_refcount member, then the
__shared_count() is called(shared_ptr_base.h:569);

Construct __shared_count's _M_Pi member, then
_Sp_counted_base() is called(shared_ptr_base.h:369);

At last, the _Sp_counted_base() is called, its _M_use_count and _M_weak_count are initialized to 1 both.

 
After
shared_ptr< Foo > f1(new Foo), the memory layout should be like this:
 


Now let's check what happened when it runs to
std::shared_ptr< Foo > sp2 = sp1:
#0  __gnu_cxx::__atomic_add_single(int*, int) () at /usr/local/include/c++/4.9.1/ext/atomicity.h:74
#1  0x0000000000400b42 in __gnu_cxx::__atomic_add_dispatch(int*, int) () at /usr/local/include/c++/4.9.1/ext/atomicity.h:98
#2  0x0000000000400f6f in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_add_ref_copy() () at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:133
#3  0x0000000000400de5 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count(std::__shared_count<(__gnu_cxx::_Lock_policy)2> const&) ()
    at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:673
#4  0x0000000000400d1d in std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::__shared_ptr(std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2> const&) ()
    at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:912
#5  0x0000000000400d43 in std::shared_ptr<Foo>::shared_ptr(std::shared_ptr<Foo> const&) () at /usr/local/include/c++/4.9.1/bits/shared_ptr.h:103
#6  0x0000000000400b86 in main () at test.cpp:16
 
Since the shared_ptr class does not implemented the assignment constructor, #4 is called. It is a shallow copy. The sp2's _M_ptr is pointed to sp1's _M_ptr. Then the __shared_ptr's _M_refcount should be constructed firstly.
The #3 is called. Now it has a given construction argument(const __shared_count& __r), as we know, the __r is sp1's _M_refcount. The sp1's _M_pi member is not empty, that is, the sp2's
_M_pi member is assigned to sp1's _M_pi as well. That is, sp1 and sp2 are sharing same _M_pi member.

In #2, the __shared_count's copy  contructor will call the
_M_add_ref_copy() method of the
_Sp_counted_base class.

_M_add_ref_copy() simply increase the reference count of the
_Sp_counted_base class in atomicity.h:74.

Now the reference count of sp1 and sp2 are 2 both since they are sharing same _M_pi member:



 
We step forward to line 21, the sp2 will be destructed:
#0  std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::swap (this=0x7fffffffe590, __other=...) at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:1069
#1  0x0000000000400e71 in std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::reset (this=0x7fffffffe5b0) at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:1015
#2  0x0000000000400bce in main () at test.cpp:21
 
The reset() calls
__shared_ptr().swap(*this), the __shared_ptr() constructs a temporary __shared_ptr object. Then the swap() exchanges its content with *this(the current __share_ptr object):
void swap(__shared_ptr<_Tp, _Lp>& __other) noexcept
{
  std::swap(_M_ptr, __other._M_ptr);//The current __share_ptr's content is NULL
  _M_refcount._M_swap(__other._M_refcount); //The current __shared_ptr's refcount is NULL as well
 
Now the temporary __shared_ptr object will be destructed as the swap() is finished. Its __shared_count member will be destructed firstly:
~__shared_count() noexcept
{
  if (_M_pi != nullptr)
    _M_pi->_M_release();
}
In _M_release(), the
__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) will be called. Now the sp1's refcount equals to 1 as sp2's refcount is decreased by 1.
 
If the sp1.reset() is called, the call stack looks like this:
#0  std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x603030) at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:149
#1  0x0000000000400da9 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=0x7fffffffe598) at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:666
#2  0x0000000000400cc8 in std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=0x7fffffffe590) at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:914
#3  0x0000000000400e7d in std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::reset (this=0x7fffffffe5c0) at /usr/local/include/c++/4.9.1/bits/shared_ptr_base.h:1015
#4  0x0000000000400c16 in main () at test.cpp:26
 
The ~shared_count() will be called also. I provide a simplified version of shared_count::_M_release() here:
void
_M_release() noexcept
{
  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();
    }
  }
}
The __exchange_and_add_dispatch(&_M_use_count, -1)
returns a value of 1, the _M_dispose() is called.
In _M_dispose(), the embeded object is deleted. And in _M_destroy(), itself this pointer is deleted as well.
 
The core theory of shared_ptr implementation is reference counter which resides in heap. If the object pointer is shared between shared_ptrs, its associated data - used counter must be shared as
well. According this skill, I wrote a lite shared_ptr which can pass the minimal function test above:
 
#ifndef _SHAREDPTR_H_
#define _SHAREDPTR_H_

#include <utility>
#include <stdint.h>

namespace adora {

template <typename T>
class atomic_integer {
public:
  atomic_integer(T t): value_(t) {}
  T add(T x) { return __atomic_add_fetch(&value_, x, __ATOMIC_SEQ_CST); }
  T get() { return __atomic_load_n(&value_, __ATOMIC_SEQ_CST); }
private:
  T value_;
};

typedef atomic_integer< int32_t > atom_int32_t;
typedef atomic_integer< int64_t > atom_int64_t;

template <typename T>
class shared_ptr {
public:
  explicit shared_ptr(T* ptr): ptr_(ptr), counter_(new atom_int32_t(1)) {}

  shared_ptr(): ptr_(nullptr), counter_(nullptr) {}

  shared_ptr(const shared_ptr<T>& r): ptr_(nullptr), counter_(nullptr) {
    if ( ptr_ != r.ptr_ ) {
      shared_ptr<T> tmp;
      swap(tmp);
      ptr_ = r.ptr_;
      counter_ = r.counter_;
      counter_->add(1);
    }
  }

  shared_ptr<T>& operator=(const shared_ptr<T> r) {
    if ( ptr_ != r.ptr_ ) {
      shared_ptr<T> tmp;
      swap(tmp);
      ptr_ = r.ptr_;
      counter_ = r.counter_;
      counter_->add(1);
    }
    return *this;
  }

  T* operator->() { return ptr_; }

  T& operator*() { return *ptr_; }

  int32_t use_count() { return ptr_ == nullptr ? 0 : counter_->get(); }

  void reset() {
    if ( ptr_ == nullptr )
      return;
    counter_->add(-1);
    if ( use_count() == 0 ) {
      delete ptr_;
      ptr_ = nullptr;
    }
  }

  void swap(shared_ptr<T>& r) {
    std::swap(ptr_, r.ptr_);
    std::swap(counter_, r.counter_);
  }

  ~shared_ptr() {
    if ( ptr_ == nullptr )
      return;
    counter_->add(-1);
    if ( use_count() == 0 && ptr_ ) {
      delete counter_;
      delete ptr_;
      ptr_ = nullptr;
    }
  }

private:
  T* ptr_;
  atom_int32_t* counter_;
};

}

#endif
http://zhan.renren.com/learnfromnowon?gid=3602888498053551195&checked=true
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  shared_ptr