shared_ptr Analysis

 

shared_ptr Analysis

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:

  1. Create a Foo object in heap(Foo*);
  2. Call __shared_ptr()(shared_ptr_base.h:871);
  3. Construct __shared_ptr's _M_refcount member, then the __shared_count() is called(shared_ptr_base.h:569);
  4. Construct __shared_count's _M_Pi member, then _Sp_counted_base() is called(shared_ptr_base.h:369);
  5. 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:

 shared_ptr Analysis

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

 

  1. 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.
  2. In #2, the __shared_count's copy  contructor will call the _M_add_ref_copy() method of the _Sp_counted_base class.
  3. _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:

shared_ptr Analysis

 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值