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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shared_ptr和unique_ptr是C++11中引入的智能指针类模板。两者都用于管理动态分配的内存资源,可以自动释放内存,避免内存泄漏问题。 shared_ptr允许多个智能指针共享同一块内存资源,通过引用计数来追踪内存的使用情况。引用计数为0时,内存会被自动释放。shared_ptr可以复制和赋值,并且支持自定义删除器。 unique_ptr是一种独占所有权的智能指针,不能复制或赋值给其他unique_ptr,但可以通过move语义来转移所有权。unique_ptr可以在运行时确定一个对象是否被释放,因此更为轻量级和高效。它还支持自定义删除器。 在使用时,我们可以根据具体情况选择shared_ptr或unique_ptr来管理动态分配的内存资源。如果多个指针需要共享资源,可以使用shared_ptr;如果只有一个指针需要管理资源,可以使用unique_ptr。这样可以提高代码的安全性和可读性。 引用提供了shared_ptr和unique_ptr对数组的支持。我们可以使用unique_ptr<A[]>来创建一个指向数组的unique_ptr对象,同样,也可以使用shared_ptr<A[]>来创建一个指向数组的shared_ptr对象。 引用说明了unique_ptr不允许复制,但可以通过move语义来转移所有权。也就是说,可以使用std::move来将一个unique_ptr转移到另一个unique_ptr中。 引用给出了一个例子,说明了不应该使用this指针来构造shared_ptr作为返回值。因为这样会导致shared_ptr的引用计数错误,从而引发内存泄漏问题。 总之,shared_ptr和unique_ptr都是用于管理动态分配的内存资源的智能指针类模板。shared_ptr允许多个指针共享资源,而unique_ptr则提供了独占所有权的功能。我们可以根据具体需求选择适合的智能指针来管理内存资源。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [c++新特性: shared_ptr和unique_ptr](https://blog.csdn.net/TOPEE362/article/details/126353662)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值