Given the several edits, I have the impression that a comprehensive summary would be useful.
1. When not to
There are two situations where you should not use smart pointers.
The first is the exact same situation in which you should not use a C++
class in fact. IE: DLL boundary if you do not offer the source code to the client. Let say anecdotal.
The second happens much more often: smart manager means ownership. You may use pointers to point at existing resources without managing their lifetime, for example:
void notowner(const std::string& name)
{
Class* pointer(0);
if (name == "cat")
pointer = getCat();
else if (name == "dog")
pointer = getDog();
if (pointer) doSomething(*pointer);
}
This example is constrained. But a pointer is semantically different from a reference in that it may point to an invalid location (the null pointer). In this case, it's perfectly fine not to use a smart pointer in its stead, because you don't want to manage the lifetime of the object.
2. Smart managers
Unless you are writing a smart manager class, if you use the keyword delete
you are doing something wrong.
It is a controversial point of view, but after having reviewed so many example of flawed code, I don't take chances any longer. So, if you write new
you need a smart manager for the newly allocated memory. And you need it right now.
It does not mean you are less of a programmer! On the contrary, reusing code that has been proved to work instead of reinventing the wheel over and over is a key skill.
Now, the real difficulty start: which smart manager ?
3. Smart pointers
There are various smart pointers out of there, with various characteristics.
Skipping std::auto_ptr
which you should generally avoid (its copy semantic is screwed).
scoped_ptr
: no overhead, cannot be copied or moved.
unique_ptr
: no overhead, cannot be copied, can be moved.
shared_ptr
/ weak_ptr
: some overhead (reference counting), can be copied.
Usually, try to use either scoped_ptr
or unique_ptr
. If you need several owners try to change the design. If you can't change the design and really need several owners, use a shared_ptr
, but beware of references cycles that ought to be broken using a weak_ptr
somewhere in the midst.
4. Smart containers
Many smart pointers are not meant to be copied, therefore their use with the STL containers are somewhat compromised.
Instead of resorting to shared_ptr
and its overhead, use smart containers from the Boost Pointer Container. They emulate the interface of classic STL containers but store pointers they own.
5. Rolling your own
There are situations when you may wish to roll your own smart manager. Do check that you did not just missed some feature in the libraries your are using beforehand.
Writing a smart manager in the presence of exceptions is quite difficult. You usually cannot assume that memory is available (new
may fail) or that Copy Constructor
s have the no throw
guarantee.
It may be acceptable, somewhat, to ignore the std::bad_alloc
exception and impose that Copy Constructor
s of a number of helpers do not fail... after all, that's what boost::shared_ptr
does for its deleter D
template parameter.
But I would not recommend it, especially for a beginner. It's a tricky issue, and you're not likely to notice the bugs right now.
6. Examples
// For the sake of short code, avoid in real code ;)
using namespace boost;
// Example classes
// Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
// It is to obey the `Cloneable` concept as described in
// the Boost Pointer Container library linked above
struct Cloneable
{
virtual ~Cloneable() {}
virtual Cloneable* clone() const = 0;
};
struct Derived: Cloneable
{
virtual Derived* clone() const { new Derived(*this); }
};
void scoped()
{
scoped_ptr<Cloneable> c(new Derived);
} // memory freed here
// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
return unique_ptr<Cloneable>(new Derived);
}
void shared()
{
shared_ptr<Cloneable> n1(new Derived);
weak_ptr<Cloneable> w = n1;
{
shared_ptr<Cloneable> n2 = n1; // copy
n1.reset();
assert(n1.get() == 0);
assert(n2.get() != 0);
assert(!w.expired() && w.get() != 0);
} // n2 goes out of scope, the memory is released
assert(w.expired()); // no object any longer
}
void container()
{
ptr_vector<Cloneable> vec;
vec.push_back(new Derived);
vec.push_back(new Derived);
vec.push_back(
vec.front().clone() // Interesting semantic, it is dereferenced!
);
} // when vec goes out of scope, it clears up everything ;)