c++boost.gif (8819 bytes)Smart pointer programming techniques

Using incomplete classes for implementation hiding
The "Pimpl" idiom
Using abstract classes for implementation hiding
Preventing delete px.get()
Using a shared_ptr to hold a pointer to an array
Encapsulating allocation details, wrapping factory functions
Using a shared_ptr to hold a pointer to a statically allocated object
Using a shared_ptr to hold a pointer to a COM object
Using a shared_ptr to hold a pointer to an object with an embedded reference count
Using a shared_ptr to hold another shared ownership smart pointer
Obtaining a shared_ptr from a raw pointer
Obtaining a shared_ptr (weak_ptr) to this in a constructor
Obtaining a shared_ptr to this
Using shared_ptr as a smart counted handle
Using shared_ptr to execute code on block exit
Using shared_ptr<void> to hold an arbitrary object
Associating arbitrary data with heterogeneous shared_ptr instances
Post-constructors and pre-destructors
Using shared_ptr as a CopyConstructible mutex lock
Using shared_ptr to wrap member function calls
Delayed deallocation
Weak pointers to objects not managed by a shared_ptr

Using incomplete classes for implementation hiding

[Old, proven technique; can be used in C]

class FILE;

FILE * fopen(char const * name, char const * mode);
void fread(FILE * f, void * data, size_t size);
void fclose(FILE * f);

[Compare with]

class FILE;

shared_ptr<FILE> fopen(char const * name, char const * mode);
void fread(shared_ptr<FILE> f, void * data, size_t size);

Note that there is no fclose function; shared_ptr's ability to execute a custom deleter makes it unnecessary.

[shared_ptr<X> can be copied and destroyed when X is incomplete.]

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);
}

[file is CopyConstructible and Assignable.]

Using abstract classes for implementation hiding

[Interface based programming]

// X.hpp:

class X
{
public:

    virtual void f() = 0;
    virtual void g() = 0;

protected:

    ~X() {}
};

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;
}

[Note protected and nonvirtual destructor; client cannot delete X; shared_ptr correctly calls ~X_impl even when nonvirtual.]

Preventing delete px.get()

[Alternative 1, use the above.]

[Alternative 2, use a private deleter:]

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;
    }
};

Using a shared_ptr to hold a pointer to an array

[...]

shared_ptr<X> px(new X[1], checked_array_deleter<X>());

[shared_array is preferable, has a better interface; shared_ptr has *, ->, derived to base conversions.]

Encapsulating allocation details, wrapping factory functions

[Existing interface, possibly allocates X from its own heap, ~X is private, or X is incomplete.]

X * CreateX();
void DestroyX(X *);

[Wrapper:]

shared_ptr<X> createX()
{
    shared_ptr<X> px(CreateX(), DestroyX);
}

[Client remains blissfully oblivious of allocation details; doesn't need to remember to call destroyX.]

Using a shared_ptr to hold a pointer to a statically allocated object

[...]

shared_ptr<X> createX();

[Sometimes needs to return a pointer to a statically allocated X instance.]

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

static X x;

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

[The same technique works for any object known to outlive the pointer.]

Using a shared_ptr to hold a pointer to a COM Object

[COM objects have an embedded reference count, AddRef() and Release(), Release() self-destroys when reference count drops to zero.]

shared_ptr<IWhatever> make_shared_from_COM(IWhatever * p)
{
    p->AddRef();
    shared_ptr<IWhatever> pw(p, mem_fn(&IWhatever::Release));
    return pw;
}

[All pw copies will share a single reference.]

Using a shared_ptr to hold a pointer to an object with an embedded reference count

[A generalization of the above. Example assumes intrusive_ptr-compatible object.]

template<class T> struct intrusive_deleter
{
    void operator()(T * p)
    {
        if(p) intrusive_ptr_release(p);
    }
};

shared_ptr<X> make_shared_from_intrusive(X * p)
{
    if(p) intrusive_ptr_add_ref(p);
    shared_ptr<X> px(p, intrusive_deleter<X>());
    return px;
}

Using a shared_ptr to hold another shared ownership smart pointer

[...]

template<class P> class smart_pointer_deleter
{
private:

    P p_;

public:

    smart_pointer_deleter(P const & p): p_(p)
    {
    }

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

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;
}

[If p_.reset() can throw - wrap in try {} catch(...) {} block, will release p_ when all weak pointers are eliminated.]

Obtaining a shared_ptr from a raw pointer

[...]

void f(X * p)
{
    shared_ptr<X> px(???);
}

[Not possible in general, either switch to]

void f(shared_ptr<X> px);

[This transformation can be used for nonvirtual member functions, too; before:]

void X::f(int m);

[after]

void f(shared_ptr<X> this_, int m);

[If f cannot be changed, use knowledge about p's lifetime and allocation details and apply one of the above.]

Obtaining a shared_ptr (weak_ptr) to this in a constructor

[...]

class X
{
public:

    X()
    {
        shared_ptr<X> this_(???);
    }
};

[Not possible in general. If X can have automatic or static storage, and this_ doesn't need to keep the object alive, use a null_deleter. If X is supposed to always live on the heap, and be managed by a shared_ptr, use:]

class X
{
private:

    X() { ... }

public:

    static shared_ptr<X> create()
    {
        shared_ptr<X> px(new X);
        // use px as 'this_'
        return px;
    }
};

Obtaining a shared_ptr to this

[Sometimes it is needed to obtain a shared_ptr from this in a virtual member function.]

[The transformations from above cannot be applied.]

class X
{
public:

    virtual void f() = 0;

protected:

    ~X() {}
};

class Y
{
public:

    virtual shared_ptr<X> getX() = 0;

protected:

    ~Y() {}
};

// --

class impl: public X, public Y
{
public:

    impl() { ... }

    virtual void f() { ... }

    virtual shared_ptr<X> getX()
    {
        shared_ptr<X> px(???);
        return px;
    }
};

[Solution:]

class impl: public X, public Y
{
private:

    weak_ptr<impl> weak_this;

    impl(impl const &);
    impl & operator=(impl const &);

    impl() { ... }

public:

    static shared_ptr<impl> create()
    {
        shared_ptr<impl> pi(new impl);
        pi->weak_this = pi;
        return pi;
    }

    virtual void f() { ... }

    virtual shared_ptr<X> getX()
    {
        shared_ptr<X> px = get_shared_ptr(weak_this);
        return px;
    }
};

[Future support planned, impl: public enable_shared_from_this<impl>.]

Using shared_ptr as a smart counted handle

[Win32 API allusion]

typedef void * HANDLE;
HANDLE CreateProcess();
void CloseHandle(HANDLE);

[Quick wrapper]

typedef shared_ptr<void> handle;

handle createProcess()
{
    shared_ptr<void> pv(CreateProcess(), CloseHandle);
    return pv;
}

[Better, typesafe:]

class handle
{
private:

    shared_ptr<void> pv_;

public:

    explicit handle(HANDLE h): pv_(h, CloseHandle) {}
    HANDLE get() { return pv_.get(); }
};

Using shared_ptr to execute code on block exit

[1. Executing f(p), where p is a pointer:]

    shared_ptr<void> guard(p, f);

[2. Executing arbitrary code: f(x, y):]

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

Using shared_ptr<void> to hold an arbitrary object

[...]

    shared_ptr<void> pv(new X);

[Will correctly call ~X.]

[Can be used to strip type information: shared_ptr<X> -> (shared_ptr<void>, typeid(X))]

Associating arbitrary data with heterogeneous shared_ptr instances

[...]

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;

Post-constructors and pre-destructors

[...]

class X
{
public:

    X();
    virtual void postconstructor();
    virtual void predestructor() throw();
    ~X() throw();

    struct deleter
    {
        void operator()(X * p)
        {
             p->predestructor();
             delete p;
        }
    }

    static shared_ptr<X> create()
    {
        shared_ptr<X> px(new X, X::deleter());
        px->postconstructor(); // can throw
        return px;
    }
};

Using shared_ptr as a CopyConstructible mutex lock

[Sometimes it's necessary to return a mutex lock from a function. A noncopyable lock cannot be used.]

class mutex
{
public:

    void lock();
    void unlock();
};

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

[Or to encapsulate it in a dedicated 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)) {}
};

[Usage:]

    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.]

Using shared_ptr to wrap member function calls

[http://www.research.att.com/~bs/wrapper.pdf]

template<class T> class pointer
{
private:

    T * p_;

public:

    explicit pointer(T * p): p_(p)
    {
    }

    shared_ptr<T> operator->() const
    {
        p_->prefix();
        return shared_ptr<T>(p_, mem_fn(&T::suffix));
    }
};

class X
{
private:

    void prefix();
    void suffix();
    friend class pointer<X>;
    
public:

    void f();
    void g();
};

int main()
{
    X x;

    pointer<X> px(&x);

    px->f();
    px->g();
}

Delayed deallocation

[In some situations, a single px.reset() can trigger an expensive deallocation in a performance-critical region.]

class X; // ~X is expensive

class Y
{
    shared_ptr<X> px;

public:

    void f()
    {
        px.reset();
    }
};

[Solution 1]

vector< shared_ptr<void> > free_list;

class Y
{
    shared_ptr<X> px;

public:

    void f()
    {
        free_list.push_back(px);
        px.reset();
    }
};

// periodically invoke free_list.clear() when convenient

[Solution 2, as above, but use a delayed deleter]

struct delayed_deleter
{
    template<class T> void operator()(T * p)
    {
        try
        {
            shared_ptr<void> pv(p);
            free_list.push_back(pv);
        }
        catch(...)
        {
        }
    }
};

Weak pointers to objects not managed by a shared_ptr

Make the object hold a shared_ptr to itself, using a null_deleter:

class X
{
private:

    shared_ptr<X> this_;
    int i_;

public:

    explicit X(int i): this_(this, null_deleter()), i_(i)
    {
    }

    // repeat in all constructors (including the copy constructor!)

    X(X const & rhs): this_(this, null_deleter()), i_(rhs.i_)
    {
    }

    // do not forget to not assign this_ in the copy assignment

    X & operator=(X const & rhs)
    {
	    i_ = rhs.i_;
    }

    weak_ptr<X> get_weak_ptr() const { return this_; }
};

When the object's lifetime ends, X::this_ will be destroyed, and all weak pointers will automatically expire.


$Date$

Copyright © 2003 Peter Dimov. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.