1. Environment
– windows xp
– gcc-4.4
– boost-1.43
2. auto_ptr
A smart pointer is an abstract data type that simulates a pointer while providing additional features, such as automatic garbage collection or bounds checking. There’s auto_ptr
in C++03 library for general use. But it’s not so easy to deal with it. You may encounter pitfalls or limitations. The main drawback of auto_ptr
is that it has the transfer-of-ownership semantic. I just walk through it. Please read comments in code carefully:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
int *test_auto_ptr_exp() { auto_ptr<int> p(new int(1)); throw runtime_error("auto_ptr test exception."); /* exception-safe, p is free even when an exception is thrown. */ return p.get(); } void test_auto_ptr_basic() { auto_ptr<int> p1(new int(1)); auto_ptr<int> p2(new int(2)); auto_ptr<int> p3(p1); auto_ptr<int> p4; p4 = p2; if (p1.get()) { /* NULL */ cout << "*p1=" << *p1 << endl; } if (p2.get()) { /* NULL */ cout << "*p2=" << *p2 << endl; } if (p3.get()) { /* ownership already transferred from p1 to p3 */ cout << "*p3=" << *p3 << endl; } if (p4.get()) { /* ownership already transferred from p2 to p4 */ cout << "*p4=" << *p4 << endl; } /* ERROR: void is a type of template specialization */ //auto_ptr<void> ptr5(new int(3)); } void test_auto_ptr_errors() { /* ERROR: statically allocated object */ const char *str = "Hello"; auto_ptr<const char> p1(str); /* ERROR: two auto_ptrs refer to the same object */ int *pi = new int(5); auto_ptr<int> p2(pi); auto_ptr<int> p3(p2.get()); p2.~auto_ptr(); /* now p3 is not available too */ /* ERROR: hold a pointer to a dynamically allocated array */ /* When destroyed, it only deletes first single object. */ auto_ptr<int> (new int[10]); /* ERROR: store an auto_ptr in a container */ //vector<auto_ptr<int> > vec; //vec.push_back(auto_ptr<int>(new int(1))); //vec.push_back(auto_ptr<int>(new int(2))); //auto_ptr<int> p4(vec[0]); /* vec[0] is assigned NULL */ //auto_ptr<int> p5; //p5 = vec[1]; /* vec[1] is assigned NULL */ } |
3. unique_ptr
To resolve the drawbacks, C++0x deprecates usage of auto_ptr
, and unique_ptr
is the replacement. unique_ptr
makes use of a new C++ langauge feature called rvalue reference which is similar to our current (left) reference (&), but spelled (&&). GCC implemented this feature in 4.3, but unique_ptr
is only available begin from 4.4.
What is rvalue?
rvalues are temporaries that evaporate at the end of the full-expression in which they live (“at the semicolon”). For example, 1729, x + y, std::string(“meow”), and x++ are all rvalues.
While, lvalues name objects that persist beyond a single expression. For example, obj, *ptr, ptr[index], and ++x are all lvalues.
NOTE: It’s important to remember: lvalueness versus rvalueness is a property of expressions, not of objects.
We may have another whole post to address the rvalue feature. Now, let’s take a look of the basic usage. Please carefully reading the comments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
unique_ptr<int> get_unique_ptr(int i) { return unique_ptr<int> (new int(i)); } void use_unique_ptr(unique_ptr<int> p) { /* p is deleted when finish running this function. */ } void test_unique_ptr_basic() { unique_ptr<int> p(new int(1)); /* * One can make a copy of an rvalue unique_ptr. * But one can not make a copy of an lvalue unique_ptr. * Note the defaulted and deleted functions usage in source code(c++0x). */ //unique_ptr<int> p2 = p; /* error */ //use_unique_ptr(p); /* error */ use_unique_ptr(move(p)); use_unique_ptr(get_unique_ptr(3)); } |
One can ONLY make a copy of an rvalue unique_ptr
. This confirms no ownership issues occur like that of auto_ptr
. Since temporary values cannot be referenced after the current expression, it is impossible for two unique_ptr
to refer to a same pointer. You may also noticed the move function. We will also discuss it in a later post.
Some more snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
struct aclass { aclass() { cout << "in aclass::ctor()" << endl; } ~aclass() { cout << "in aclass::dtor()" << endl; } }; struct aclass_deleter { void operator()(void *p) { delete static_cast<aclass *> (p); } }; template<class T> struct array_deleter { void operator()(T *array) { delete[] array; } }; typedef array_deleter<aclass> aclass_array_deleter; typedef unique_ptr<aclass, aclass_array_deleter> aclass_array_ptr; void test_unique_ptr_custom_deleter() { /* * Hold a pointer to a dynamically allocated array. * aaptr & aaptr2 are deleted when finish running this function. */ aclass_array_ptr aaptr(new aclass[3]); unique_ptr<aclass[]> aaptr2(new aclass[3]); /* default_deleter<T[]> */ /* allow void pointer, but a custom deleter must be used. */ unique_ptr<void, aclass_deleter> p3(new aclass); } |
unique_ptr
can hold pointers to an array. unique_ptr
defines deleters to free memory of its internal pointer. There are pre-defined default_deleter
using delete and delete[](array) for general deallocation. You can also define your customized ones. In addition, a void
type can be used.
NOTE: To compile the code, you must specify the -std=c++0x
flag.
4. shared_ptr
A shared_ptr
is used to represent shared ownership; that is, when two pieces of code needs access to some data but neither has exclusive ownership (in the sense of being responsible for destroying the object). A shared_ptr
is a kind of counted pointer where the object pointed to is deleted when the use count goes to zero.
Following snippet shows the use count changes when using shared_ptr
. The use count changes from 0 to 3, then changes back to 0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
struct bclass { int i; bclass(int i) { this->i = i; } virtual ~bclass() { cout << "in bclass::dtor() with i=" << i << endl; } }; struct cclass: bclass { cclass(int i) : bclass(i) { } virtual ~cclass() { cout << "in cclass::dtor() with i=" << i << endl; } }; void use_shared_ptr(shared_ptr<int> p) { cout << "count=" << p.use_count() << endl; } void test_shared_ptr_basic() { shared_ptr<int> p; cout << "count=" << p.use_count() << endl; p.reset(new int(1)); cout << "count=" << p.use_count() << endl; shared_ptr<int> p2 = p; cout << "count=" << p.use_count() << endl; use_shared_ptr(p2); cout << "count=" << p.use_count() << endl; p2.~shared_ptr(); cout << "count=" << p.use_count() << endl; p2.~shared_ptr(); cout << "count=" << p.use_count() << endl; } |
Snippets showing pointer type conversion:
1 2 3 4 5 6 7 8 9 10 |
void test_shared_ptr_convertion() { /* p is deleted accurately without custom deleter */ shared_ptr<void> p(new aclass); /* use parent type to hold child object */ shared_ptr<bclass> p2(new cclass(10)); shared_ptr<cclass> p3 = static_pointer_cast<cclass> (p2); cout << "p3->i=" << p3->i << endl; p3->i = 20; cout << "p2->i=" << p2->i << endl; } |
The void
type can be used directly without a custom deleter, which is required in unique_ptr
. Actually, shared_ptr
has already save the exact type info in its constructor. Refer to source code for details :). And static_pointer_cast
function is used to convert between pointer types.
Unlike auto_ptr
, Since shared_ptr
can be shared, it can be used in STL containers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
typedef shared_ptr<bclass> bclass_ptr; struct bclass_ops { void operator()(const bclass_ptr& p) { cout << p->i << endl; } bool operator()(const bclass_ptr& a, const bclass_ptr& b) { return a->i < b->i; } }; void test_shared_ptr_containers() { vector<bclass_ptr> vec1, vec2; bclass_ptr ptr(new bclass(1)); vec1.push_back(ptr); vec2.push_back(ptr); ptr.reset(new bclass(2)); vec1.push_back(ptr); vec2.push_back(ptr); ptr.reset(new bclass(3)); vec1.push_back(ptr); vec2.push_back(ptr); for_each(vec1.begin(), vec1.end(), bclass_ops()); reverse(vec2.begin(), vec2.end()); for_each(vec2.begin(), vec2.end(), bclass_ops()); } |
NOTE: shared_ptr
is available in both TR1 and Boost library. You can use either of them, for their interfaces are compatible. In addition, there are dual C++0x and TR1 implementation. The TR1 implementation is considered relatively stable, so is unlikely to change unless bug fixes require it.
5. weak_ptr
weak_ptr
objects are used for breaking cycles in data structures. See snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct mynode { int i; shared_ptr<mynode> snext; weak_ptr<mynode> wnext; mynode(int i) { this->i = i; } ~mynode() { cout << "in mynode::dtor() with i=" < i < endl; } }; void test_weak_ptr() { shared_ptr<mynode> head(new mynode(1)); head->snext = shared_ptr<mynode>(new mynode(2)); /* use weak_ptr to solve cyclic dependency */ //head->snext = head; head->wnext = head; } |
If we use uncomment to use shared_ptr
, head is not freed since there still one reference to it when exiting the function. By using weak_ptr
, this code works fine.
6. scoped_ptr
scoped_ptr
template is a simple solution for simple needs. It supplies a basic “resource acquisition is initialization” facility, without shared-ownership or transfer-of-ownership semantics.
This class is only available in Boost. Since unique_ptr
is already there in C++0x, this class may be thought as redundant. Snippet is also simple:
1 2 3 4 |
void test_scoped_ptr() { /* simple solution for simple needs */ scoped_ptr<aclass> p(new aclass); } |
Complete and updated code can be found on google code host here. I use conditional compilation to swith usage between TR1 and Boost implementation in code. Hope you find it useful.