Smart pointers


Smart pointers are objects which store pointers to dynamically allocated
(heap) objects. They behave much like built-in C++ pointers except that
they automatically delete the object pointed to at the appropriate time.
Smart pointers are particularly useful in the face of exceptions as they
ensure proper destruction of dynamically allocated objects. They can also
be used to keep track of dynamically allocated objects shared by multiple
owners.

Conceptually, smart pointers are seen as owning the object pointed to, and
thus responsible for deletion of the object when it is no longer needed.

The smart pointer library provides five smart pointer class templates:

scoped_ptr      <boost/scoped_ptr.hpp>
    Simple sole ownership of single objects. Noncopyable.

scoped_array    <boost/scoped_array.hpp>
    Simple sole ownership of arrays. Noncopyable.

shared_ptr      <boost/shared_ptr.hpp>
    Object ownership shared among multiple pointers

shared_array    <boost/shared_array.hpp>
    Array ownership shared among multiple pointers.

weak_ptr        <boost/weak_ptr.hpp>
    Non-owning observers of an object owned by shared_ptr.

intrusive_ptr   <boost/intrusive_ptr.hpp>
    Shared ownership of objects with an embedded reference count.


Requirements
============

Scoped_ptr requires that T be a complete type at destruction time,
but shared_ptr does not.
Destructor of T and T::operator delete must not throw exception!


Scoped pointer
==============

Similar to auto_ptr, but no ownership transfer. Noncopyable. Non-part of ::tr1

namespace boost {
  template<class T> class scoped_ptr : noncopyable {
   public:
     typedef T element_type;
     explicit scoped_ptr(T * p = 0); // never throws
     ~scoped_ptr(); // never throws
     void reset(T * p = 0); // never throws
     T & operator*() const; // never throws
     T * operator->() const; // never throws
     T * get() const; // never throws
     void swap(scoped_ptr & b); // never throws
  };
  template<class T>
  void swap(scoped_ptr<T> & a, scoped_ptr<T> & b); // never throws

}


#include <boost/scoped_ptr.hpp>
#include <iostream>

struct Shoe { ~Shoe() { std::cout << "Buckle my shoe\n"; } };

class MyClass {
    boost::scoped_ptr<int> ptr;
  public:
    MyClass() : ptr(new int) { *ptr = 0; }
    int add_one() { return ++*ptr; }
};

int main()
{
    boost::scoped_ptr<Shoe> x(new Shoe);
    MyClass my_instance;
    std::cout << my_instance.add_one() << '\n';
    std::cout << my_instance.add_one() << '\n';
}  // the destructor is called here or at release()

The example program produces the beginning of a child's nursery rhyme:

1
2
Buckle my shoe


Shared pointer
==============

Reference counted pointer. CopyConstructible and Assignable. Part of ::tr1

shared_ptr<T> can be implicitly converted to shared_ptr<U> whenever T*
can be implicitly converted to U*. In particular, shared_ptr<T> is implicitly
convertible to shared_ptr<T const>, to shared_ptr<U> where U is an accessible
base of T, and to shared_ptr<void>.

namespace boost {
  class bad_weak_ptr: public std::exception;
  template<class T> class weak_ptr;

  template<class T> class shared_ptr {
    public:
      typedef T element_type;

      shared_ptr(); // never throws
      template<class Y> explicit shared_ptr(Y * p);
      template<class Y, class D> shared_ptr(Y * p, D d);
      ~shared_ptr(); // never throws

      shared_ptr(shared_ptr const & r); // never throws
      template<class Y> shared_ptr(shared_ptr<Y> const & r); // never throws
      template<class Y> explicit shared_ptr(weak_ptr<Y> const & r);
      template<class Y> explicit shared_ptr(std::auto_ptr<Y> & r);

      shared_ptr & operator=(shared_ptr const & r); // never throws  
      template<class Y> shared_ptr & operator=(shared_ptr<Y> const & r); // never throws
      template<class Y> shared_ptr & operator=(std::auto_ptr<Y> & r);

      void reset(); // never throws
      template<class Y> void reset(Y * p);
      template<class Y, class D> void reset(Y * p, D d);

      T & operator*() const; // never throws
      T * operator->() const; // never throws
      T * get() const; // never throws

      bool unique() const; // never throws
      long use_count() const; // never throws

      operator unspecified-bool-type() const; // never throws

      void swap(shared_ptr & b); // never throws
  };

  template<class T, class U>
    bool operator==(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws
  template<class T, class U>
    bool operator!=(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws
  template<class T, class U>
    bool operator<(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws
  template<class T> void swap(shared_ptr<T> & a, shared_ptr<T> & b); // never throws
  template<class T> T * get_pointer(shared_ptr<T> const & p); // never throws
  template<class T, class U>
    shared_ptr<T> static_pointer_cast(shared_ptr<U> const & r); // never throws
  template<class T, class U>
    shared_ptr<T> const_pointer_cast(shared_ptr<U> const & r); // never throws
  template<class T, class U>
    shared_ptr<T> dynamic_pointer_cast(shared_ptr<U> const & r); // never throws
  template<class E, class T, class Y>
    std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, shared_ptr<Y> const & p);
  template<class D, class T>
    D * get_deleter(shared_ptr<T> const & p);
}

shared_ptr objects offer the same level of thread safety as built-in types.
A shared_ptr instance can be "read" (accessed using only const operations)
simultaneously by multiple threads. Different shared_ptr instances can be
"written to" (accessed using mutable operations such as operator= or reset)
simultaneosly by multiple threads (even when these instances are copies,
and share the same reference count underneath.)

Any other simultaneous accesses result in undefined behavior.


Weak pointer
============

Pointer to a shared_ptr owned object. Copy constructible and Assignable.
Part of ::tr1

The weak_ptr class template stores a "weak reference" to an object that's
already managed by a shared_ptr. To access the object, a weak_ptr can be
converted to a shared_ptr using the shared_ptr constructor or the member
function lock. When the last shared_ptr to the object goes away and the
object is deleted, the attempt to obtain a shared_ptr from the weak_ptr
instances that refer to the deleted object will fail: the constructor will
throw an exception of type boost::bad_weak_ptr, and weak_ptr::lock will
return an empty shared_ptr.

weak_ptr operations never throw exceptions.

namespace boost {
  template<class T> class weak_ptr {
    public:
      typedef T element_type;

      weak_ptr();
      template<class Y> weak_ptr(shared_ptr<Y> const & r);
      weak_ptr(weak_ptr const & r);
      template<class Y> weak_ptr(weak_ptr<Y> const & r);
      ~weak_ptr();

      weak_ptr & operator=(weak_ptr const & r);
      template<class Y> weak_ptr & operator=(weak_ptr<Y> const & r);
      template<class Y> weak_ptr & operator=(shared_ptr<Y> const & r);

      long use_count() const;
      bool expired() const;     // use_count() == 0
      shared_ptr<T> lock() const;
      // return expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

      void reset();
      void swap(weak_ptr<T> & b);
  };

  template<class T, class U>
    bool operator<(weak_ptr<T> const & a, weak_ptr<U> const & b);
  template<class T>
    void swap(weak_ptr<T> & a, weak_ptr<T> & b);
}


Intrusive pointer
=================

Pointer to an object with a reference counter. Cheap. Non ::tr1

namespace boost {
  template<class T> class intrusive_ptr {
    public:
      typedef T element_type;
      intrusive_ptr(); // never throws
      intrusive_ptr(T * p, bool add_ref = true);
      intrusive_ptr(intrusive_ptr const & r);
      template<class Y> intrusive_ptr(intrusive_ptr<Y> const & r);
      ~intrusive_ptr();

      intrusive_ptr & operator=(intrusive_ptr const & r);
      template<class Y> intrusive_ptr & operator=(intrusive_ptr<Y> const & r);
      template<class Y> intrusive_ptr & operator=(T * r);

      T & operator*() const; // never throws
      T * operator->() const; // never throws
      T * get() const; // never throws

      operator unspecified-bool-type() const; // never throws
      void swap(intrusive_ptr & b); // never throws
  };
  template<class T, class U>
    bool operator==(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws
  template<class T, class U>
    bool operator!=(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws
  template<class T>
    bool operator==(intrusive_ptr<T> const & a, T * b); // never throws
  template<class T>
    bool operator!=(intrusive_ptr<T> const & a, T * b); // never throws
  template<class T>
    bool operator==(T * a, intrusive_ptr<T> const & b); // never throws
  template<class T>
    bool operator!=(T * a, intrusive_ptr<T> const & b); // never throws
  template<class T, class U>
    bool operator<(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws
  template<class T> void swap(intrusive_ptr<T> & a, intrusive_ptr<T> & b); // never throws
  template<class T> T * get_pointer(intrusive_ptr<T> const & p); // never throws
  template<class T, class U>
    intrusive_ptr<T> static_pointer_cast(intrusive_ptr<U> const & r); // never throws
  template<class T, class U>
    intrusive_ptr<T> const_pointer_cast(intrusive_ptr<U> const & r); // never throws
  template<class T, class U>
    intrusive_ptr<T> dynamic_pointer_cast(intrusive_ptr<U> const & r); // never throws
  template<class E, class T, class Y>
    std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, intrusive_ptr<Y> const & p);
}


Implementations
===============

The implementation questions revolved around the reference count which
must be kept, either attached to the pointed to object, or detached
elsewhere. Each of those variants have themselves two major variants:

Direct detached: the shared_ptr contains a pointer to the object,
    and a pointer to the count.

Indirect detached: the shared_ptr contains a pointer to a helper object,
    which in turn contains a pointer to the object and the count.

Embedded attached: the count is a member of the object pointed to.

Placement attached: the count is attached via operator new manipulations.


Some applications
=================

The Pimpl idiom
===============

// file.hpp:
class file
{
private:
    class impl;
    shared_ptr<impl> pimpl_;
public:
    file(char const * name, char const * mode);
    // compiler generated members are fine and useful
    void read(void * data, size_t size);
};

// file.cpp:
#include "file.hpp"
class file::impl
{
private:
    impl(impl const &);
    impl & operator=(impl const &);
    // private data
public:
    impl(char const *name, char const *mode) { ... }
    ~impl() { ... }
    void read(void * data, size_t size) { ... }
};

file::file(char const *name, char const *mode)
                        : pimpl_(new impl(name, mode)) { }

void file::read(void *data, size_t size)
{
    pimpl_->read(data, size);
}

The key thing to note here is that the compiler-generated copy constructor,
assignment operator, and destructor all have a sensible meaning. As a result,
file is CopyConstructible and Assignable, allowing its use in standard
containers.


Abstract classes
================

// X.h
// abstract base class: "interface"
class X
{
public:
    virtual void f() = 0;
    virtual void g() = 0;
protected:
    ~X() {}     // nonvirtual protected
};

// factory function
shared_ptr<X> createX();

// X.cpp
class X_impl: public X
{
private:
    X_impl(X_impl const &);
    X_impl & operator=(X_impl const &);
public:
    virtual void f()
    {
      // ...
    }
    virtual void g()
    {
      // ...
    }
};

shared_ptr<X> createX()
{
    shared_ptr<X> px(new X_impl);
    return px;
}

A key property of shared_ptr is that the allocation, construction,
deallocation, and destruction details are captured at the point of
construction, inside the factory function. Note the protected and
nonvirtual destructor in the example above. The client code cannot,
and does not need to, delete a pointer to X; the shared_ptr<X>
instance returned from createX will correctly call ~X_impl


Preventing delete px.get()
==========================

class X
{
private:
    ~X();
    class deleter;
    friend class deleter;
    class deleter
    {
    public:
        void operator()(X * p) { delete p; }
    };
public:
    static shared_ptr<X> create()
    {
        shared_ptr<X> px(new X, X::deleter());
        return px;
    }
};


Pointer to a non-heap object
============================

struct null_deleter
{
    void operator()(void const *) const { }
};

static X x;

shared_ptr<X> createX()
{
    shared_ptr<X> px(&x, null_deleter());
    return px;
}


Pointer holding other smart pointer
===================================

template<class P> struct smart_pointer_deleter
{
private:
    P p_;
public:
    smart_pointer_deleter(P const & p): p_(p) { }

    void operator()(void const *) { p_.reset(); }

    P const & get() const { return p_; }
};

shared_ptr<X> make_shared_from_another(another_ptr<X> qx)
{
    shared_ptr<X> px(qx.get(), smart_pointer_deleter< another_ptr<X> >(qx));
    return px;
}

One subtle point is that deleters are not allowed to throw exceptions,
and the above example as written assumes that p_.reset() doesn't throw.
If this is not the case, p_.reset() should be wrapped in a
try {} catch(...) {} block that ignores exceptions.


Execute code on leaving a block
===============================

// executes f(x,y);
shared_ptr<void> guard(static_cast<void*>(0), bind(f, x, y));

bind(f, 1, 2) will produce a "nullary" function object that takes
no arguments and returns f(1, 2). Similarly, bind(g, 1, 2, 3)() is
equivalent to g(1, 2, 3).


To hold arbitrary data
======================

shared_ptr<void> can act as a generic object pointer similar to void*.
When a shared_ptr<void> instance constructed as:

    shared_ptr<void> pv(new X);

is destroyed, it will correctly dispose of the X object by executing ~X.

This propery can be used in much the same manner as a raw void* is used
to temporarily strip type information from an object pointer.
A shared_ptr<void> can later be cast back to the correct type by using
static_pointer_cast.


Associative containers
======================

hared_ptr and weak_ptr support operator< comparisons required by standard
associative containers such as std::map. This can be used to non-intrusively
associate arbitrary data with objects managed by shared_ptr:

typedef int Data;

std::map< shared_ptr<void>, Data > userData;
// or std::map< weak_ptr<void>, Data > userData; to not affect the lifetime

shared_ptr<X> px(new X);
shared_ptr<int> pi(new int(3));

userData[px] = 42;
userData[pi] = 91;


Mutex
=====

class mutex
{
public:
    void lock();
    void unlock();
};

shared_ptr<mutex> lock(mutex & m)
{
    m.lock();
    return shared_ptr<mutex>(&m, mem_fn(&mutex::unlock));
}

Better yet, the shared_ptr instance acting as a lock can be encapsulated
in a dedicated shared_lock class:

class shared_lock
{
private:
    shared_ptr<void> pv;
public:
    template<class Mutex> explicit shared_lock(Mutex & m)
                  : pv((m.lock(), &m), mem_fn(&Mutex::unlock)) {}
};

shared_lock can now be used as:

    shared_lock lock(m);

Note that shared_lock is not templated on the mutex type, thanks to
shared_ptr<void>'s ability to hide type information.



Timing
======

Five smart pointer implementation strategies were tested:
Counted pointer using a heap allocated reference count, this is referred
    - simple counted.
Counted pointer using a special purpose allocator for the reference count
    - special counted.
Counted pointer using an intrusive reference count
    - intrusive.
Linked pointer to list all the shared_ptr's sharing a common pointer
    - linked.
Cyclic pointer, a counted implementation using a std::deque for
allocation with provision for weak pointers and garbage collection
of cycles of pointers
    - cyclic.

GCC
                initialization      copy operation

simple counted    4620 +/- 150        301 +/- 28

special counted   1990 +/- 40         264 +/- 7

intrusive         1590 +/- 70         181 +/- 12

linked            1470 +/- 140        345 +/- 26

cyclic            2180 +/- 100        330 +/- 18

dumb              1590 +/- 70         74 +/- 12

raw               1430 +/- 60         27 +/- 11


The timing results mainly speak for themselves: clearly an intrusive pointer
outperforms all others and a simple heap based counted pointer has poor
performance relative to other implementations. The selection of an optimal
non-intrusive smart pointer implementation is more application dependent,
however. Where small numbers of copies are expected, it is likely that the
linked implementation will be favoured. Conversely, for larger numbers of
copies a counted pointer with some type of special purpose allocator looks
like a win.