Smart pointers from boost
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. Not in std::
namespace boost {
template<class T> class scoped_ptr : noncopyable {
public:
typedef T element_type;
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<class T>
void swap(scoped_ptr<T> & a, scoped_ptr<T> & b);
}
#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 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();
template<class Y> explicit shared_ptr(Y * p);
template<class Y, class D> shared_ptr(Y * p, D d);
~shared_ptr();
shared_ptr(shared_ptr const & r);
template<class Y> shared_ptr(shared_ptr<Y> const & r);
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);
template<class Y> shared_ptr & operator=(shared_ptr<Y> const & r);
template<class Y> shared_ptr & operator=(std::auto_ptr<Y> & r);
void reset();
template<class Y> void reset(Y * p);
template<class Y, class D> void reset(Y * p, D d);
T & operator*() const;
T * operator->() const;
T * get() const;
bool unique() const;
long use_count() const;
operator unspecified-bool-type() const;
void swap(shared_ptr & b);
};
template<class T, class U>
bool operator==(shared_ptr<T> const & a, shared_ptr<U> const & b);
template<class T, class U>
bool operator!=(shared_ptr<T> const & a, shared_ptr<U> const & b);
template<class T, class U>
bool operator<(shared_ptr<T> const & a, shared_ptr<U> const & b);
template<class T> void swap(shared_ptr<T> & a, shared_ptr<T> & b);
template<class T> T * get_pointer(shared_ptr<T> const & p);
template<class T, class U>
shared_ptr<T> static_pointer_cast(shared_ptr<U> const & r);
template<class T, class U>
shared_ptr<T> const_pointer_cast(shared_ptr<U> const & r);
template<class T, class U>
shared_ptr<T> dynamic_pointer_cast(shared_ptr<U> const & r);
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.
std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1;
p1.reset();
p2.reset();
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;
shared_ptr<T> lock() const;
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);
}
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> sp1,sp2;
std::weak_ptr<int> wp;
sp1 = std::make_shared<int> (20);
wp = sp1;
sp2 = wp.lock();
sp1.reset();
sp1 = wp.lock();
std::cout << "*sp1: " << *sp1 << '\n';
std::cout << "*sp2: " << *sp2 << '\n';
return 0;
}
std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1;
{
std::shared_ptr<int> p2 = wp1.lock();
if(p2)
{
}
}
p1.reset();
std::shared_ptr<int> p3 = wp1.lock();
if(p3)
{
}
Intrusive pointer
=================
Pointer to an object with a reference counter. Cheap. Not in std::
The intrusive pointer is a light version of the shared pointer.
It assumes that the dynamically allocated object it has to point
to implements a reference counter by itself. This maintains the
counter in a single place and keeps the smart pointer to the minimum
size (the size of the raw pointer).
In order to define an intrusive pointer for a given object, you must
first create two functions that manage its embedded reference counter.
One is intrusive_ptr_add_ref, which simply increases the counter;
the other is intrusive_ptr_release, which decreases the counter and,
if it reaches zero, releases the object.
#include <cstdlib>
#include <iostream>
#include <boost/intrusive_ptr.hpp>
class some_resource
{
size_t m_counter;
public:
some_resource(void) : m_counter(0)
{
std::cout << "Resource created" << std::endl;
}
~some_resource(void)
{
std::cout << "Resource destroyed" << std::endl;
}
size_t refcnt(void)
{
return m_counter;
}
void ref(void)
{
m_counter++;
}
void unref(void)
{
m_counter--;
}
};
void intrusive_ptr_add_ref(some_resource* r)
{
r->ref();
std::cout << "Resource referenced: " << r->refcnt() << std::endl;
}
void intrusive_ptr_release(some_resource* r)
{
r->unref();
std::cout << "Resource unreferenced: " << r->refcnt() << std::endl;
if (r->refcnt() == 0)
delete r;
}
int main(void)
{
boost::intrusive_ptr<some_resource> r(new some_resource);
boost::intrusive_ptr<some_resource> r2(r);
std::cout << "Program exiting" << std::endl;
return EXIT_SUCCESS;
Smart pointer 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.
---- ----------------
| px | ------------------------------> | object |
| py | ------- ----------------
---- | ^
| |
| ----------- |
-------->| refCount | |
| weakCount | |
| pz |-------------
-----------
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.
Remarks:
- Never create shared_ptr from this.
- Costs: ptr.unique() cheap but ptr.use_count() may be expensive
- When creating cyclic data structures use at least one weak_ptr
Some applications
The Pimpl idiom
===============
class file
{
private:
class impl;
shared_ptr<impl> pimpl_;
public:
file(char const * name, char const * mode);
void read(void * data, size_t size);
};
#include "file.hpp"
class file::impl
{
private:
impl(impl const &);
impl & operator=(impl const &);
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
================
class X
{
public:
virtual void f() = 0;
virtual void g() = 0;
protected:
~X() {}
};
shared_ptr<X> createX();
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
===============================
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;
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.
Make shared
===========
shared_ptr<std::string> x = make_shared<std::string>("hello, world!");
Enable_shared_from_this
=======================
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <cassert>
class Y: public boost::enable_shared_from_this<Y>
{
public:
boost::shared_ptr<Y> f()
{
return shared_from_this();
}
};
int main()
{
boost::shared_ptr<Y> p(new Y);
boost::shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p));
}