Smart Pointers



Item 42. Smart Pointers

We C++ programmers are a loyal bunch. Whenever we're faced with a situation that requires a feature the language doesn't support, we don't abandon C++ to flirt with some other language; we just extend C++ to support the feature in which we're interested.

For instance, it's often the case that you'll need something that behaves like a pointer, but a built-in pointer type just doesn't do the job. In those cases, a C++ programmer will use a "smart pointer." (See also Function Objects [18, 63] for similar observations about function pointers.)

A smart pointer is a class type that is tricked up to look and act like a pointer but that provides additional capability beyond that provided by a built-in pointer. Generally, a smart pointer uses the capabilities provided by a class's constructors, destructor, and copy operations to control access to or keep track of what it points to in a way that a built-in pointer cannot.

All smart pointers overload the -> and * operators so that they can be used with standard pointer syntax. (Some rare specimens even go so far as to overload the ->* operator; see Pointers to Class Members Are Not Pointers [15, 53].) Other smart pointers (in particular, smart pointers used as STL iterators) overload other pointer operators, like ++, --, +, -, +=, -=, and [] (see Pointer Arithmetic [44, 149]). Smart pointers are often implemented as class templates so that they may refer to different types of objects. Here's a very simple smart pointer template that performs a check that it's not null before use:

template <typename T>
class CheckedPtr {
  public:
    explicit CheckedPtr( T *p ) : p_( p ) {}
    ~CheckedPtr() { delete p_; }
    T *operator ->() { return get(); }
    T &operator *() { return *get(); }
  private:
    T *p_; // what we're pointing to
    T *get() { // check ptr before returning it
        if( !p_ )
            throw NullCheckedPointer();
        return p_;
    }
    CheckedPtr( const CheckedPtr & );
    CheckedPtr &operator =( const CheckedPtr & );
};

Use of a smart pointer should be straightforward, mimicking the use of a built-in pointer:

CheckedPtr<Shape> s( new Circle );
s->draw(); // same as (s.operator ->())->draw()

The key to this façade is the overloaded operator ->;. The -> operator must be overloaded as a member and has a rather unusual property in that it is not "consumed" when it is called. In other words, when we write s->draw(), the compiler recognizes that s is not a pointer but a class object with an overloaded operator -> (that is, that s is a smart pointer). This results in a call to the member overloaded operator, which returns (in this case) a Shape * built-in pointer. This pointer is then used to call Shape's draw function. If you write this out longhand, you'll get the following challenging expression: (s.operator ->())->draw(), which contains two uses of the -> operator, one overloaded, one built in.

Smart pointers also typically overload operator * as well as operator -> so that they may be used to refer to nonclass types.

CheckedPtr<int> ip = new int;
*ip = 12; // same as ip.operator *() = 12
(*s).draw(); // use on ptr to class, too

Smart pointers are used pervasively in C++ programming, from resource handles (see RAII [40, 139] and auto_ptr Is Unusual [43, 147]) to STL iterators, to reference counting pointers, to wrappers around pointers to member functions, and on and on. Semper fidelis.