td::auto_ptr was a move semantics pioneer. However, at that time, C++ didn't have the facilities for supporting move operations that were both safe and efficient. After years of distilling, C++0x brings you a superior alternative called unique_ptr. unique_ptr offers the same runtime and size efficiency of auto_ptr in addition to code safety and many other goodies, including array handling, and STL compatibility.
![]()
Your program needs a smart pointer that implements the strict ownership semantics but you cannot use auto_ptr safely with STL containers and algorithms.
![]()
Replace auto_ptr with the new unique_ptr class template.
Presenting the Problem
Suppose your program calls std::sort() to sort a sequence of auto_ptr objects:
vector<auto_ptr<int> > vi;
//..populate vi
sort(vi.begin(), vi.end(), indirect_less());
Depending on the underlying implementation of sort(), the above call could work perfectlyor it might cause a runtime crash. The problem is that some implementations of sort() pick an element out of the sequence, storing a local copy thereof:
value_type pivot_elem = *midpoint;//not a copy operation!
sort() assumes that pivot_elem and *midpoint are equivalent. However when value_type is an auto_ptr, this assumption fails because what appears to be a copy operation is in fact a move operation. Consequently, the algorithm fails. There's nothing amiss with the implementation of sort(). Rather, auto_ptr is the culprit.
Because of such bugs, using auto_ptr with Standard Library containers and algorithms is forbidden. What other alternative do you have?
Just Like auto_ptr, But Better
unique_ptr ticks the right boxes. It implements a strict ownership semantics, it's slim and it's fast, with a memory footprint identical to that of a native pointer.
If you've used auto_ptr before you will notice that unique_ptr has an almost identical interface:
#include <utility>
using namespace std;
unique_ptr<int> up1; //default construction
unique_ptr<int> up2(new int(9)); //initialize with pointer
*up2 = 23; //dereference
up2.reset(); //reset
assert (sizeof(auto_ptr<T>)==sizeof(unique_ptr<T>));
assert (sizeof(auto_ptr<T>)==sizeof(unique_ptr&lT>));
The main difference between auto_ptr and unique_ptr becomes visible in move operations. While auto_ptr sometimes disguises move operations as copy-operations, unique_ptr will not let you use copy semantics when you're actually moving an lvalue unique_ptr:
auto_ptr<int> ap1(new int);
auto_ptr<int> ap2=ap1; //OK but unsafe: move
//operation in disguise
unique_ptr<int> up1(new int);
unique_ptr<int> up2=up1; //compilation error: private
//copy ctor inaccessible
Instead, you must call move() when moving operation from an lvalue:
unique_ptr<int> up2 = std::move(up1);//OK
This makes unique_ptr safe for usage with containers and algorithms.
Containers and Algorithms
If a container of unique_ptr objects moves elements internally instead of copying them, your code will work without problems. If, however, a container or an algorithm uses copy semantics for its value_type you will get a compilation error. Thus, you won't encounter a run time error due to a move operation being mistaken for a copy:
vector<unique_ptr<int> > vi;
v.push_back(unique_ptr<int>(new int(8)));
v.push_back(unique_ptr<int>(new int(4)));
v.push_back(unique_ptr<int>(new int(1)));
sort(v.begin(),v.end(),indirect_less()); //result {1,4,8}
The Sink and Source Idiom
A source is a function that acquires a resource and passes the ownership of that resource to another function called a sink. A sink uses the resource and when it's done, it disposes of that resource. You can return unique_ptr by value from a source:
unique_ptr<int> source(int i)
{
return unique_ptr<int>(new int(i));
}
void sink(unique_ptr<int>);
sink(source(5));//OK, implicit move from rvalue
Again, when moving from an lvalue you must use move() explicitly:
unique_ptr<int> up=source(5);
sink(up); //compilation error, implicit move from lvalue
sink(move(up)); //OK
To generalize, an implicit move from an rvalue unique_ptr is always safe and therefore may use copy syntax. Moving from an lvalue unique_ptr requires an explicit move() call.
Customizable Deleters
A deleter is a function that the smart pointer uses for deallocating its resource. By default, unique_ptr's deleter uses plain delete, as expected. However, certain resources require special cleanup operations. Here's how you install a custom deleter that has no effect:
struct nop_deleter
{
template <class T> void operator()(T*) {}; // do nothing
};
int main()
{
int n=0;
// stack memory. Safe, deleter does nothing
unique_ptr<int, nop_deleter> pi(&n);
*pi = 10;
assert(sizeof(pi)==sizeof(int*)); //zero space overhead
}
In the following example, a custom deleter calls std::free() to deallocate an object allocated using std::malloc():
#include <cstdlib>
double* d =(double*) std::malloc(sizeof(double));
//deleter calls free(), not delete
unique_ptr<int, void (*)(void*)> pd(d, std::free);
Arrays
unique_ptr<int[]> arrup (new int[3]);
arrup[0]=1;
arrup[1]=2;
arrup[2]=3;
The trailing [] after the type in the unique_ptr declaration indicates that the "array version" (no dereference, no conversions, indexing supported) shall be used. As expected, the default deleter here is delete[].
Elegant Smart
unique_ptr isn't just a safer alternative to the deprecated auto_ptr. It offers services that auto_ptr doesn't support, including safe usage with containers and algorithms, customizable deleters, and array handling. Surprisingly, this versatility doesn't exact a performance toll; you only pay for what you're actually using.
| DevX is a division of Internet.com. © Copyright 2010 Internet.com. All Rights Reserved. Legal Notices |