【转】shared_ptr
from: http://www.codeproject.com/Articles/541067/Cplusplus-Smart-Pointers
Introduction
Ooops. Yet another article on smart pointers of C++11. Nowadays I hear a lot of people talking about the new C++ standard which is nothing but C++0x/C++11. I went through some of the language features of C++11 and it's really an amazing work. I'll focus only on the smart pointers section of C++11.
Background
What are the issues with normal/raw/naked pointers?
Let's go one by one.
People refrain from using pointers as they give a lot of issues if not handled properly. That's why newbie programmers dislike pointers. Many issues are involved with pointers like ensuring the lifetime of objects referred to by pointers, dangling references, and memory leaks.
Dangling reference is caused if a memory block is pointed by more than one pointer variable and if one of the pointers is released without letting know the other pointer. As all of you know, memory leaks occur when a block of memory is fetched from the heap and is not released back.
People say, I write clean and error proof code, why should I use smart pointers? And a programmer asked me, "Hey, here is my code. I fetched the memory from the heap, manipulated it, and after that I released it properly. What is the need of a smart pointer? "
Hide Copy Codevoid Foo( ) { int* iPtr = new int[5]; //manipulate the memory block . . . delete[ ] iPtr; }
The above code works fine and memory is released properly under ideal circumstances. But think of the practical environment of code execution. The instructions between memory allocation and releasing can do nasty things like accessing an invalid memory location, dividing by zero, or say another programmer pitching into your program to fix a bug and adding a premature
returnstatement based on some condition.
In all the above cases, you will never reach the point where the memory is released. This is because the first two cases throw an exception whereas the third one is a premature return. So the memory gets leaked while the program is running.
The one stop solution for all of the above issues is Smart Pointers [if they are really smart enough].
What is a smart pointer?
Smart pointer is a RAII modeled class to manage dynamically allocated memory. It provides all the interfaces provided by normal pointers with a few exceptions. During construction, it owns the memory and releases the same when it goes out of scope. In this way, the programmer is free about managing dynamically allocated memory.
C++98 has introduced the first of its kind called
auto_ptr.
auto_ptr
Let's see the use of
auto_ptrand how smart it is to resolve the above issues. Hide Copy Code
class Test { public: Test(int a = 0 ) : m_a(a) { } ~Test( ) { cout<<"Calling destructor"<<endl; } public: int m_a; }; void main( ) { std::auto_ptr<Test> p( new Test(5) ); cout<<p->m_a<<endl; }
The above code is smart to release the memory associated with it. What we did is, we fetched a memory block to hold an object of type
Testand associated it with
auto_ptr p. So when
pgoes out of scope, the associated memory block is also released. Hide Shrink Copy Code
//*************************************************************** class Test { public: Test(int a = 0 ) : m_a(a) { } ~Test( ) { cout<<"Calling destructor"<<endl; } public: int m_a; }; //*************************************************************** void Fun( ) { int a = 0, b= 5, c; if( a ==0 ) { throw "Invalid divisor"; } c = b/a; return; } //*************************************************************** void main( ) { try { std::auto_ptr<Test> p( new Test(5) ); Fun( ); cout<<p->m_a<<endl; } catch(...) { cout<<"Something has gone wrong"<<endl; } }
In the above case, an exception is thrown but still the pointer is released properly. This is because of stack unwinding which happens when an exception is thrown. As all local objects belonging to the
tryblock are destroyed,
pgoes out of scope and it releases the associated memory.
Issue 1: So far
auto_ptris smart. But it has more fundamental flaws over its smartness.
auto_ptrtransfers the ownership when it is assigned to another
auto_ptr. This is really an issue while passing the
auto_ptrbetween the functions. Say, I have an
auto_ptrin
Foo( )and this pointer is passed another function say
Fun( )from
Foo. Now once
Fun( )completes its execution, the ownership is not returned back to
Foo. Hide Copy Code
//*************************************************************** class Test { public: Test(int a = 0 ) : m_a(a) { } ~Test( ) { cout<<"Calling destructor"<<endl; } public: int m_a; }; //*************************************************************** void Fun(auto_ptr<Test> p1 ) { cout<<p1->m_a<<endl; } //*************************************************************** void main( ) { std::auto_ptr<Test> p( new Test(5) ); Fun(p); cout<<p->m_a<<endl; }
The above code causes a program crash because of the weird behavior of
auto_ptr. What happens is that,
powns a memory block and when
Funis called,
ptransfers the ownership of its associated memory block to the
auto_ptr
p1which is the copy of
p. Now
p1owns the memory block which was previously owned by
p. So far it is fine. Now
funhas completed its execution, and
p1goes out of scope and the memory blocked is released. How about
p?
pdoes not own anything, that is why it causes a crash when the next line is executed which accesses
pthinking that it owns some resource.
Issue 2: Yet another flaw.
auto_ptrcannot be used with an array of objects. I mean it cannot be used with the operator
new[]. Hide Copy Code
//*************************************************************** void main( ) { std::auto_ptr<Test> p(new Test[5]); }
The above code gives a runtime error. This is because when
auto_ptrgoes out of scope,
deleteis called on the associated memory block. This is fine if
auto_ptrowns only a single object. But in the above code, we have created an array of objects on the heap which should be destroyed using
delete[ ]and not
delete.
Issue 3:
auto_ptrcannot be used with standard containers like vector, list, map, etc.
As
auto_ptris more error prone and it will be deprecated, C++ 11 has come with a new set of smart pointers, each has its own purpose.
shared_ptr
unique_ptr
weak_ptr
shared_ptr
OK, get ready to enjoy the real smartness. The first of its kind is
shared_ptrwhich has the notion called shared ownership. The goal of
shared_ptris very simple: Multiple shared pointers can refer to a single object and when the last shared pointer goes out of scope, memory is released automatically.
Creation:
Hide Copy Codevoid main( ) { shared_ptr<int> sptr1( new int ); }
Make use of the
make_sharedmacro which expedites the creation process. As
shared_ptrallocates memory internally, to hold the reference count,
make_shared( )is implemented in a way to do this job effectively. Hide Copy Code
void main( ) { shared_ptr<int> sptr1 = make_shared<int>(100); }
The above code creates a
shared_ptrwhich points to a memory block to hold an integer with value 100 and reference count 1. If another shared pointer is created out of
sptr1, the reference count goes up to 2. This count is known as strong reference. Apart from this, the shared pointer has another reference count known as weak reference, which will be explained while visiting weak pointers.
You can find out the number of
shared_ptrs referring to the resource by just getting the reference count by calling
use_count( ). And while debugging, you can get it by watching the
stong_refof the
shared_ptr.
Destruction:
shared_ptrreleases the associated resource by calling
deleteby default. If the user needs a different destruction policy, he/she is free to specify the same while constructing the
shared_ptr. The following code is a source of trouble due to the default destruction policy: Hide Copy Code
class Test { public: Test(int a = 0 ) : m_a(a) { } ~Test( ) { cout<<"Calling destructor"<<endl; } public: int m_a; }; void main( ) { shared_ptr<Test> sptr1( new Test[5] ); }
Because
shared_ptrowns an array of objects, it calls
deletewhen it goes out of scope. Actually,
delete[ ]should have been called to destroy the array. The user can specify the custom deallocator by a callable object, i.e., a function, lambda expression, function object. Hide Copy Code
void main( ) { shared_ptr<Test> sptr1( new Test[5], [ ](Test* p) { delete[ ] p; } ); }
The above code works fine as we have specified the destruction should happen via
delete[].
Interface
shared_ptrprovides dereferencing operators
*,
->like a normal pointer provides. Apart from that it provides some more important interfaces like:
-
get( )
: To get the resource associated with theshared_ptr
. -
reset( )
: To yield the ownership of the associated memory block. If this is the lastshared_ptr
owning the resource, then the resource is released automatically. -
unique
: To know whether the resource is managed by only thisshared_ptr
instance. -
operator bool
: To check whether theshared_ptr
owns a memory block or not. Can be used with anif
condition.
OK, that is all about
shared_ptrs. But
shared_ptrs too have a few issues:.
Issues:
- [li]If a memory is block is associated with
shared_ptr
s belonging to a different group, then there is an error. Allshared_ptr
s sharing the same reference count belong to a group. Let's see an example.
void main( ) { shared_ptr<int> sptr1( new int ); shared_ptr<int> sptr2 = sptr1; shared_ptr<int> sptr3; sptr3 = sptr2; }
The below table gives you the reference count values for the above code.
All
shared_ptrs share the same reference count hence belonging to the same group. The above code is fine. Let's see another piece of code. Hide Copy Code
void main( ) { int* p = new int; shared_ptr<int> sptr1( p); shared_ptr<int> sptr2( p ); }
The above piece of code is going to cause an error because two
shared_ptrs from different groups share a single resource. The below table gives you a picture of the root cause.
To avoid this, better not create the shared pointers from a naked pointer.
- [li]There is another issue involved with creating a shared pointer from a naked pointer. In the above code, consider that only one shared pointer is created using
p
and the code works fine. Consider by mistake if a programmer deletes the naked pointerp
before the scope of the shared pointer ends. Oooppss!!! Yet another crash..
Cyclic Reference: Resources are not released properly if a cyclic reference of shared pointers are involved. Consider the following piece of code.[/li]
class B; class A { public: A( ) : m_sptrB(nullptr) { }; ~A( ) { cout<<" A is destroyed"<<endl; } shared_ptr<B> m_sptrB; }; class B { public: B( ) : m_sptrA(nullptr) { }; ~B( ) { cout<<" B is destroyed"<<endl; } shared_ptr<A> m_sptrA; }; //*********************************************************** void main( ) { shared_ptr<B> sptrB( new B ); shared_ptr<A> sptrA( new A ); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; }
The above code has cyclic reference. I mean class A holds a shared pointer to B and class B holds a shared pointer to A. In this case, the resource associated with both
sptrAand
sptrBare not released. Refer to the below table.
Reference counts for both
sptrAand
sptrBgo down to 1 when they go out of scope and hence the resources are not released!!!!!
To resolve the cyclic reference, C++ provides another smart pointer class called
weak_ptr.
Weak_Ptr
A weak pointer provides sharing semantics and not owning semantics. This means a weak pointer can share a resource held by a
shared_ptr. So to create a weak pointer, some body should already own the resource which is nothing but a shared pointer.
A weak pointer does not allow normal interfaces supported by a pointer, like calling
*,
->. Because it is not the owner of the resource and hence it does not give any chance for the programmer to mishandle it. Then how do we make use of a weak pointer?
The answer is to create a
shared_ptrout of a
weak _ptrand use it. Because this makes sure that the resource won't be destroyed while using by incrementing the strong reference count. As the reference count is incremented, it is sure that the count will be at least 1 till you complete using the
shared_ptrcreated out of the
weak_ptr. Otherwise what may happen is while using the
weak_ptr, the resource held by the
shared_ptrgoes out of scope and the memory is released which creates chaos.
Creation
A weak pointer constructor takes a shared pointer as one of its parameters. Creating a weak pointer out of a shared pointer increases the weak reference counter of the shared pointer. This means that the shared pointer shares it resource with another pointer. But this counter is not considered to release the resource when the shared pointer goes out of scope. I mean if the strong reference of the shared pointer goes to 0, then the resource is released irrespective of the weak reference value.
Hide Copy Codevoid main( ) { shared_ptr<Test> sptr( new Test ); weak_ptr<Test> wptr( sptr ); weak_ptr<Test> wptr1 = wptr; }
We can watch the reference counters of the shared/weak pointer.
Assigning a weak pointer to another weak pointer increases the weak reference count.
So what happens when a weak pointer points to a resource held by the shared pointer and the shared pointer destroys the associated resource when it goes out of scope? The weak pointer gets expired.
How to check whether the weak pointer is pointing to a valid resource? There are two ways:
- Call the
use_count( )
method to know the count. Note that this method returns the strong reference count and not the weak reference. - Call the
expired( )
method. This is faster than callinguse_count( )
.
To get a
shared_ptrfrom a
weak_ptrcall
lock( )or directly casting the
weak_ptrto
shared_ptr. Hide Copy Code
void main( ) { shared_ptr<Test> sptr( new Test ); weak_ptr<Test> wptr( sptr ); shared_ptr<Test> sptr2 = wptr.lock( ); }
Getting the
shared_ptrfrom the
weak_ptrincreases the strong reference as said earlier.
Now let's see how the cyclic reference issue is resolved using the
weak_ptr. Hide Shrink Copy Code
class B; class A { public: A( ) : m_a(5) { }; ~A( ) { cout<<" A is destroyed"<<endl; } void PrintSpB( ); weak_ptr<B> m_sptrB; int m_a; }; class B { public: B( ) : m_b(10) { }; ~B( ) { cout<<" B is destroyed"<<endl; } weak_ptr<A> m_sptrA; int m_b; }; void A::PrintSpB( ) { if( !m_sptrB.expired() ) { cout<< m_sptrB.lock( )->m_b<<endl; } } void main( ) { shared_ptr<B> sptrB( new B ); shared_ptr<A> sptrA( new A ); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; sptrA->PrintSpB( ); }
Unique_ptr
This is almost a kind of replacement to the error prone
auto_ptr.
unique_ptrfollows the exclusive ownership semantics, i.e., at any point of time, the resource is owned by only one
unique_ptr. When
auto_ptrgoes out of scope, the resource is released. If the resource is overwritten by some other resource, the previously owned resource is released. So it guarantees that the associated resource is released always.
Creation
unique_ptris created in the same way as
shared_ptrexcept it has an additional facility for an array of objects. Hide Copy Code
unique_ptr<int> uptr( new int );
The
unique_ptrclass provides the specialization to create an array of objects which calls
delete[ ]instead of
deletewhen the pointer goes out of scope. The array of objects can be specified as a part of the template parameter while creating the
unique_ptr. In this way, the programmer does not have to provide a custom deallocator, as
unique_ptrdoes it. Hide Copy Code
unique_ptr<int[ ]> uptr( new int[5] );
Ownership of the resource can be transferred from one
unique_ptrto another by assigning it.
Keep in mind that
unique_ptrdoes not provide you copy semantics [copy assignment and copy construction is not possible] but move semantics.
In the above case, if
upt3and
uptr5owns some resource already, then it will be destroyed properly before owning a new resource.
Interface
The interface that
unique_ptrprovides is very similar to the ordinary pointer but no pointer arithmetic is allowed.
unique_ptrprovides a function called
releasewhich yields the ownership. The difference between
release( )and
reset( ), is
releasejust yields the ownership and does not destroy the resource whereas
resetdestroys the resource.
Which one to use?
It purely depends upon how you want to own a resource. If shared ownership is needed then go for
shared_ptr, otherwise
unique_ptr.
Apart from that,
shared_ptris a bit heavier than
unique_ptrbecause internally it allocates memory to do a lot of book keeping like strong reference, weak reference, etc. But
unique_ptrdoes not need these counters as it is the only owner for the resource.
Using the code
I have attached the worked out code to explain the details of each pointer. I have added enough comments to each instruction. Ping me back if you find any problems with the code. The weak pointer example demonstrates the problems with shared pointers in the case of cyclic reference and how the weak pointer resolves it.
阅读更多- c++ shared_ptr 使用注意事项. 2
- shared_ptr 最简单应用
- shared_ptr 之循环引用 weak_ptr
- std::auto_ptr boost::shared_ptr智能指针的应用
- shared_ptr的一些尴尬
- boost::weak_ptr和enable_shared_from_this
- shared_ptr / weak_ptr 代码片段
- Item 19: 使用srd::shared_ptr来管理共享所有权的资源
- smart_ptr之shared_ptr
- shared_ptr 的使用及注意事项
- boost库之shared_ptr
- shared_ptr的线程安全性
- 实现一个简单的shared_ptr
- 【C++】智能指针shared_ptr 定位删除器(仿函数)
- shared_ptr
- Boost智能指针——shared_ptr
- 智能指针(模拟实现AutoPtr、ScopedPtr、SharedPtr)
- C++:Boost库智能指针_shared_ptr
- c++中的std::shared_ptr和std::weak_ptr
- Effective Modern C++ 条款19 用std::shared_ptr管理共享所有权的资源