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