Objects life-cycle is crucial. A mistake in determining an object’s lifecycle can lead to resource (e.g., memory, fd) leaks as the resource owned cannot be properly released and recycled for future use. When the leak accumulates to a certain level, it crashes the whole system.
Objects life-cycle is also complicated since the ownership of one object might be relinquished by, transferred to, or shared with different entities which include but are not limited to variables, function arguments, modules, data structures, containers, and threads. Again, the resource has to be released and recycled by one of the owners at some undetermined point.
There is no de-facto standard to determine objects life-cycle. Utilities like GC (garbage collection) that is used in Java, ARC used in Objective-C and all those pointers (ptrs) in C++, all have their pros and cons. However, this article is not about pros and cons but is focused on C++ resource management helper classes, Smart Pointer,
A smart pointer is a wrapper class of a normal pointer. Smart point defines life-cycle with a reference count that reflects how many time the smart pointer object is referenced.
Next, I will show a simple implementation of a smart pointer. The code is for demonstration purposes only, thus, there is no sanity check, no exception handling and no thread-safety guarantee.
To briefly explain the code above:
SmartPointer‘s life-cycle is no more than that of an ordinary class. Thus, logic flow going out of a (function) scope destructs it;
SmartPointerhas two properties,
_refCount, both are allocated from heap. Thus, logic flow going out of a (function) scope DOES NOT destruct them;
- each time a
SmartPointeris constructed with a valid
- each time a
SmartPointeris destructed, in our case, by a logic flow going out of a scope, the
- however, the destruction of
SmartPointerdoes not necessarily lead to a destruction of
_refCount is still larger than
SmartPointer simply reduce the
_refCount and print
b) only when
_refCount is set to
0 by the minus,
SmartPointer destructs the resource referred by
_pRes and and print
So smart pointers work as handles that are used by different parts of a program to keep track and to control the resource instance. When all handles are destroyed, the resource is considered “not used”, and is deleted as well. In the end of this article, I will show some real handles that embody smart pointer in real world.
The sample showcases the usage of smart pointer in program that is linear, which is rarely the case in real scenario. Rather, as mentioned before, the resource (i.e., the instance of
AClass) can be shared, by multiple data structure and variables in parallel.
shared_ptr is the std’s implementation of smart pointer that is more robust than the demo code listed above. And it does not generate dodgy log.
An automatic pointer, though looks similar to smart pointer, is totally different. It is a convenient helper class that destructs the resource whenever the logic flow going out of the scope, just in case a programmer forgets. To some extent, it makes a pointer (that refers to a memory chunk dynamically allocated in runtime) works similar to a stack variable (statically allocated in compiling time).
Example, AutoPointer v1.0:
As given by the code snippet above, automatic pointer works internally like a simplified smart pointer that deallocates the resource regardless of the reference count (in fact, there is no reference count at all).
The coredump shows a major drawback of the automatic pointer: the ownership can not be transferred (to l1() ). As a result, even though the resource has been deallocate in l1(), main()still consider itself as the owner of automatic pointer and deallocates the pointer one time more.
How about implementing the copy constructor as well as the assignment operator so the ownership can be properly transferred?
Example, AutoPointer v2.0:
All seems good. Yet it is another example of “fixing one bug leads to another”.
The new problem is that the two semantics, ownership-transferring and copy, are coupled. So it is not compatible to some of the library functions such as
std::sort that takes one extra copy (as pivot in quick sort) as it destroys the previous one that is still in use. The detailed explanation of the problem can be found here, and thanks patatahooligan for pointing out the mistake in the original implementation.
std::auto_ptr is the std implementation of the automatic pointer. As discussed above, it is either not very interesting or problematic, so it is now deprecated. And we should use
std::unique_ptr is the std’s replacement of
std::auto_ptr in C++11. With the newly added rvalue and move semantics, the ownership of a
unique_ptr can be safely transferred to another entity. Moreover, the copy semantic is disabled for
unique_ptrs to avoid ambiguity we saw in AutoPointer v2.0. Like automatic pointer, the last owner of the pointer is responsible for deallocation.
As shown in the code snippet above, the unique pointer is preserved across different owners. When the ownership has been moved to
l1() does not deallocates the resource anymore. This gains unique pointer a much wider usage.
N.b., I would rather believe unique pointer is the major reason of the introduction of the new move semantic. Because compared to the improvement gained here, the optimization enabled by move and rvalue is less significant.
“I can understand the stuffs, but I’m not sure if I still remember them exactly next morning.”
Sure. I will find some real world counterparts to enhance your memory.
std::shared_ptr is like a handle of a video game console.
The console (resource) is “shared” by multiple players with handles, and the game should continue even if there is only one player left. Thus, “Game over” only when all players stop playing.
std::unique_ptr is like a portable game console.
One player at a time, and one should “move” it to let another to play. “Game over” when the LAST player stops playing.
std::auto_ptr is a
as it can not be easily moved.