From d04757128c02a9822bcf5a96a85bc87cff265a66 Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Tue, 21 Jan 2003 15:55:59 +0000 Subject: [PATCH] shared_ptr techniques initial commit [SVN r16978] --- sp_techniques.html | 689 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 689 insertions(+) create mode 100644 sp_techniques.html diff --git a/sp_techniques.html b/sp_techniques.html new file mode 100644 index 0000000..cc214b8 --- /dev/null +++ b/sp_techniques.html @@ -0,0 +1,689 @@ + + + + Smart pointer programming techniques + + + +

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 = make_shared(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.

+ +