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 @@ + + +
+
+ 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
+
[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.]
[...]
++// 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.]
[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.]
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; + } +}; ++
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.]
[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
.]
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.]
+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.]
+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; +} ++
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.]
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.]
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; + } +}; ++
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>
.]
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(); } +}; ++
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)); ++
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))
]
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; ++
[...]
++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; + } +}; ++
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.]
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(); +} ++
[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(...) + { + } + } +}; ++
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.
+ +