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.