How Does the Smart_ptr Library Improve Your Programs?
Automatic lifetime management of objects with shared_ptr makes shared
ownership of resources effective and safe.
Safe observation of shared resources through weak_ptr avoids dangling
pointers.
Scoped resources using scoped_ptr and scoped_array make the code easier
to write and maintain, and helps in writing exception-safe code.
Smart pointers solve the problem of managing the lifetime of resources (typically
dynamically allocated objects
[1]
). Smart pointers come in different flavors. Most
share one key featureautomatic resource management. This feature is manifested in
different ways, such as lifetime control over dynamically allocated objects, and
acquisition and release of resources (files, network connections). The Boost smart
pointers primarily cover the first casethey store pointers to dynamically allocated
objects, and delete those objects at the right time. You might wonder why these
smart pointers don't do more. Couldn't they just as easily cover all types of
resource management? Well, they could (and to some extent they do), but not
without a price. General solutions often imply increased complexity, and with the
Boost smart pointers, usability is of even higher priority than flexibility. However,
through the support for custom deleters, Boost's arguably smartest smart pointer
(boost::shared_ptr) supports resources that need other destruction code than delete.
The five smart pointer types in Boost.Smart_ptr are tailor-made to fit the most
common needs that arise in everyday programming.
[1]
Just about any type of resource can be handled by a generic smart pointer type.
resource at all.
Safe and efficient smart pointers are vital weapons in the programmer's arsenal.
Although the C++ Standard Library offers std::auto_ptr, that's not nearly enough to
fulfill our smart pointer needs. For example, auto_ptrs cannot be used as elements
of STL containers. The Boost smart pointer classes fill a gap currently left open by
the Standard.
The main focus of this chapter is on scoped_ptr, shared_ptr, intrusive_ptr, and
weak_ptr. Although the complementary scoped_array and shared_array are
sometimes useful, they are not used nearly as frequently, and they are so similar to
those covered that it would be too repetitive to cover them at the same level of
detail.
How Does Smart_ptr Fit with the Standard Library?
The Smart_ptr library has been proposed for inclusion in the Standard Library, and
there are primarily three reasons for this:
The Standard Library currently offers only auto_ptr, which is but one type of
smart pointer, covering only one part of the smart pointer spectrum.
shared_ptr offers different, arguably even more important, functionality.
The Boost smart pointers are specifically designed to work well with, and be
a natural extension to, the Standard Library. For example, before shared_ptr,
there were no standard smart pointers that could be used as elements in
containers.
Real-world programmers have proven these smart pointer classes through
heavy use in their own programs for a long time.
The preceding reasons make the Smart_ptr library a very useful addition to the
public:
explicit scoped_ptr(T* p = 0);
~scoped_ptr();
void reset(T* p = 0);
T& operator*() const;
T* operator->() const;
T* get() const;
void swap(scoped_ptr& b);
};
template<typename T>
void swap(scoped_ptr<T> & a, scoped_ptr<T> & b);
}
Members
explicit scoped_ptr(T* p=0)
The constructor stores a copy of p. Note that p must be allocated using
operator new, or be null. There is no requirement on T to be a complete type
at the time of construction. This is useful when the pointer p is the result of calling
some allocation function rather than calling new directly: Because the type needn't
be complete, a forward declaration of the type T is enough. This constructor never
throws.
~scoped_ptr()
Deletes the pointee. The type T must b
e a complete type when it is destroyed. If the
scoped_ptr holds no resource at the time of its destruction, this does nothing.
The destructor never throws.
void reset(T* p=0);
Resetting a scoped_ptr deletes the stored pointer it already owns, if any, and
then saves p. Often, the lifetime management of a resource is completely left to be
This function offers the preferred means by which to exchange the contents of two
scoped pointers. It is preferable because swap(scoped1,scoped2) can be
applied generically (in templated code) to many pointer types, including raw
pointers and third-party smart pointers.
[2]
scoped1.swap(scoped2) only
works on smart pointers, not on raw pointers, and only on those that define the
operation.
[2]
You can create your own free swap function for third-party smart pointers that
weren't smart enough to provide their own.
Usage
A scoped_ptr
is used like an ordinary pointer with a few important differences;
the most important are that you don't have to remember to invoke delete on the
pointer and that copying is disallowed. The typical operators for pointer operations
(operator* and operator->) are overloaded to provide the same syntactic
access as that for a raw pointer. Using scoped_ptrs are just as fast as using raw
pointers, and there's no size overhead, so use them extensively. To use
boost::scoped_ptr, include the header "boost/scoped_ptr.hpp".
When declaring a scoped_ptr, the type of the pointee is the parameter to the
class template. For example, here's a scoped_ptr that wraps a pointer to
std::string:
boost::scoped_ptr<std::string> p(new std::string("Hello"));
When a scoped_ptr is destroyed, it calls delete on the pointer that it owns.
No Need to Manually Delete
Let's take a look at a program that uses a scoped_ptr to manage a pointer to
std::string. Note how there's no call to delete, as the scoped_ptr is an
automatic variable and is therefore destroyed as it goes out of scope.
of ownership. auto_ptr willingly transfers ownershipaway from the source
auto_ptrwhen copied, whereas a scoped_ptr cannot be copied. Take a look
at the following program, which shows scoped_ptr and auto_ptr side by
side to clearly show how they differ.
void scoped_vs_auto() {
using boost::scoped_ptr;
using std::auto_ptr;
scoped_ptr<std::string> p_scoped(new std::string("Hello"));
auto_ptr<std::string> p_auto(new std::string("Hello"));
p_scoped->size();
p_auto->size();
scoped_ptr<std::string> p_another_scoped=p_scoped;
auto_ptr<std::string> p_another_auto=p_auto;
p_another_auto->size();
(*p_auto).size();
}
This example doesn't compile because a scoped_ptr cannot be copy
constructed or assigned to. The auto_ptr
can be both copy constructed and copy
assigned, but that also means that it transfers ownership from p_auto to
p_another_auto, leaving p_auto with a null pointer after the assignment.
This can lead to unpleasant surprises, such as when trying to store auto_ptrs in
a container.
[3]
If we remove the assignment to p_another_scoped, the
program compiles cleanly, but it results in undefined behavior at runtime, because
of dereferencing the null pointer in p_auto (*p_auto).
[3]
Never, ever, store auto_ptrs in Standard Library containers. Typically, you'll
get a compiler error if you try; if you don't, you're in trouble.
struct impl;
class pimpl_sample {
impl* pimpl_;
public:
pimpl_sample();
~pimpl_sample();
void do_something();
};
#endif
That's the interface for the class pimpl_sample. The struct impl
is forward
declared, and it holds all private members and functions in the
implementation file.
The effect is that clients are fully insulated from the internal details of the
pimpl_sample class.
// pimpl_sample.cpp
#include "pimpl_sample.hpp"
#include <string>
#include <iostream>
struct pimpl_sample::impl {
void do_something_() {
std::cout << s_ << "\n";
}
std::string s_;
};
pimpl_sample::pimpl_sample()
: pimpl_(new impl) {
pimpl_->s_ = "This is the pimpl idiom";
}
pimpl_sample::~pimpl_sample() {
Finally, it's worth noting that if the pimpl instance can be safely shared between
instances of the enclosing class (here, pimpl_sample), then
boost::shared_ptr is the right choice for handling the pimpl's lifetime. The
advantages of using shared_ptr rather than scoped_ptr includes being
relieved from manually defining the copy constructor and copy assignment
operator, and to define an empty destructorshared_ptr is designed to work
correctly even with incomplete types.
scoped_ptr
Is Not the Same As const auto_ptr
The observant reader has probably already noted that an auto_ptr
can indeed be
made to work almost like a scoped_ptr, by declaring the auto_ptr const:
const auto_ptr<A> no_transfer_of_ownership(new A);
It's close, but not quite the same. The big difference is that a scoped_ptr can be
reset, effectively deleting and replacing the pointee when needed. That's not
possible with a const auto_ptr. Another difference, albeit smaller, is the
difference in names: Although const auto_ptr essentially makes the same
statement as scoped_ptr, it does so more verbosely and less obviously. After
you have scoped_ptr in your vocabulary, you should use it because it clearly
declares your intentions. If you want to say that a resource is scoped, and that
there's no way you'll relinquish ownership of it, spell it boost::scoped_ptr.
Summary
Raw pointers complicate writing exception-safe and error-
free code. Automatically
limiting the lifetime of dynamically allocated objects to a certain scope via smart
pointers is a powerful way to address
those issues and also increase the readability,
maintainability, and quality of your code. scoped_ptr unambiguously states
that its pointee cannot be shared or transferred. As you've seen, std::auto_ptr