diff --git a/doc/smart_ptr/techniques.adoc b/doc/smart_ptr/techniques.adoc index 5f26036..f6da9f6 100644 --- a/doc/smart_ptr/techniques.adoc +++ b/doc/smart_ptr/techniques.adoc @@ -1,5 +1,5 @@ //// -Copyright 2017 Peter Dimov +Copyright 2003, 2017 Peter Dimov Distributed under the Boost Software License, Version 1.0. @@ -14,3 +14,755 @@ http://www.boost.org/LICENSE_1_0.txt :toc-title: :idprefix: techniques_ +[#techniques_incomplete] +## Using incomplete classes for implementation hiding + +A proven technique (that works in C, too) for separating interface from implementation is to use a pointer to an incomplete class as an opaque handle: + +``` +class FILE; + +FILE * fopen(char const * name, char const * mode); +void fread(FILE * f, void * data, size_t size); +void fclose(FILE * f); +``` + + +It is possible to express the above interface using `shared_ptr`, eliminating the need to manually call `fclose`: + +``` +class FILE; + +shared_ptr fopen(char const * name, char const * mode); +void fread(shared_ptr f, void * data, size_t size); +``` + +This technique relies on `shared_ptr`’s ability to execute a custom deleter, eliminating the explicit call to `fclose`, and on the fact that `shared_ptr` can be copied and destroyed when `X` is incomplete. + +## The "Pimpl" idiom + +A {cpp} specific variation of the incomplete class pattern is the "Pimpl" idiom. The incomplete class is not exposed to the user; it is hidden behind a forwarding facade. `shared_ptr` can be used to implement a "Pimpl": + +``` +// file.hpp: + +class file +{ +private: + + class impl; + shared_ptr 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. + +## Using abstract classes for implementation hiding + +Another widely used C++ idiom for separating inteface and implementation is to use abstract base classes and factory functions. +The abstract classes are sometimes called "interfaces" and the pattern is known as "interface-based programming". Again, +`shared_ptr` can be used as the return type of the factory functions: + +``` +// X.hpp: + +class X +{ +public: + + virtual void f() = 0; + virtual void g() = 0; + +protected: + + ~X() {} +}; + +shared_ptr 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 createX() +{ + shared_ptr 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` instance returned from `createX` will correctly call `~X_impl`. + +## Preventing `delete px.get()` + +It is often desirable to prevent client code from deleting a pointer that is being managed by `shared_ptr`. The previous technique showed one possible approach, using a protected destructor. Another alternative is to 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 create() + { + shared_ptr px(new X, X::deleter()); + return px; + } +}; +``` + +## Encapsulating allocation details, wrapping factory functions + +`shared_ptr` can be used in creating {cpp} wrappers over existing C style library interfaces that return raw pointers from their factory functions +to encapsulate allocation details. As an example, consider this interface, where `CreateX` might allocate `X` from its own private heap, `~X` may +be inaccessible, or `X` may be incomplete: + + X * CreateX(); + void DestroyX(X *); + +The only way to reliably destroy a pointer returned by `CreateX` is to call `DestroyX`. + +Here is how a `shared_ptr`-based wrapper may look like: + + shared_ptr createX() + { + shared_ptr px(CreateX(), DestroyX); + return px; + } + +Client code that calls `createX` still does not need to know how the object has been allocated, but now the destruction is automatic. + +[#techniques_static] +## Using a shared_ptr to hold a pointer to a statically allocated object + +Sometimes it is desirable to create a `shared_ptr` to an already existing object, so that the `shared_ptr` does not attempt to destroy the +object when there are no more references left. As an example, the factory function: + + shared_ptr createX(); + +in certain situations may need to return a pointer to a statically allocated `X` instance. + +The solution is to use a custom deleter that does nothing: + +``` +struct null_deleter +{ + void operator()(void const *) const + { + } +}; + +static X x; + +shared_ptr createX() +{ + shared_ptr 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 + +Background: COM objects have an embedded reference count and two member functions that manipulate it. `AddRef()` increments the count. +`Release()` decrements the count and destroys itself when the count drops to zero. + +It is possible to hold a pointer to a COM object in a `shared_ptr`: + + shared_ptr make_shared_from_COM(IWhatever * p) + { + p->AddRef(); + shared_ptr pw(p, mem_fn(&IWhatever::Release)); + return pw; + } + +Note, however, that `shared_ptr` copies created from `pw` will not "register" in the embedded count of the COM object; +they will share the single reference created in `make_shared_from_COM`. Weak pointers created from `pw` will be invalidated when the last +`shared_ptr` is destroyed, regardless of whether the COM object itself is still alive. + +As link:../../libs/bind/mem_fn.html#Q3[explained] in the `mem_fn` documentation, you need to `#define BOOST_MEM_FN_ENABLE_STDCALL` first. + +[#techniques_intrusive] +## Using a shared_ptr to hold a pointer to an object with an embedded reference count + +This is a generalization of the above technique. The example assumes that the object implements the two functions required by `<>`, +`intrusive_ptr_add_ref` and `intrusive_ptr_release`: + +``` +template struct intrusive_deleter +{ + void operator()(T * p) + { + if(p) intrusive_ptr_release(p); + } +}; + +shared_ptr make_shared_from_intrusive(X * p) +{ + if(p) intrusive_ptr_add_ref(p); + shared_ptr px(p, intrusive_deleter()); + return px; +} +``` + +## Using a shared_ptr to hold another shared ownership smart pointer + +One of the design goals of `shared_ptr` is to be used in library interfaces. It is possible to encounter a situation where a library takes a +`shared_ptr` argument, but the object at hand is being managed by a different reference counted or linked smart pointer. + +It is possible to exploit `shared_ptr`’s custom deleter feature to wrap this existing smart pointer behind a `shared_ptr` facade: + +``` +template 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 make_shared_from_another(another_ptr qx) +{ + shared_ptr px(qx.get(), smart_pointer_deleter< another_ptr >(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. In the (usually unlikely) event +when an exception is thrown and ignored, `p_` will be released when the lifetime of the deleter ends. This happens when all references, including +weak pointers, are destroyed or reset. + +Another twist is that it is possible, given the above `shared_ptr` instance, to recover the original smart pointer, using `<>`: + +``` +void extract_another_from_shared(shared_ptr px) +{ + typedef smart_pointer_deleter< another_ptr > deleter; + + if(deleter const * pd = get_deleter(px)) + { + another_ptr qx = pd->get(); + } + else + { + // not one of ours + } +} +``` + +[#techniques_from_raw] +## Obtaining a shared_ptr from a raw pointer + +Sometimes it is necessary to obtain a `shared_ptr` given a raw pointer to an object that is already managed by another `shared_ptr` instance. Example: + + void f(X * p) + { + shared_ptr px(???); + } + +Inside `f`, we'd like to create a `shared_ptr` to `*p`. + +In the general case, this problem has no solution. One approach is to modify `f` to take a `shared_ptr`, if possible: + + void f(shared_ptr px); + +The same transformation can be used for nonvirtual member functions, to convert the implicit `this`: + + void X::f(int m); + +would become a free function with a `shared_ptr` first argument: + + void f(shared_ptr this_, int m); + +If `f` cannot be changed, but `X` uses intrusive counting, use `<>` described above. Or, if it's known that the `shared_ptr` created in `f` will never outlive the object, use <>. + +## Obtaining a shared_ptr (weak_ptr) to this in a constructor + +Some designs require objects to register themselves on construction with a central authority. When the registration routines take a `shared_ptr`, this leads to the question how could a constructor obtain a `shared_ptr` to `this`: + +``` +class X +{ +public: + + X() + { + shared_ptr this_(???); + } +}; +``` + +In the general case, the problem cannot be solved. The `X` instance being constructed can be an automatic variable or a static variable; it can be created on the heap: + + shared_ptr px(new X); + +but at construction time, `px` does not exist yet, and it is impossible to create another `shared_ptr` instance that shares ownership with it. + +Depending on context, if the inner `shared_ptr this_` doesn't need to keep the object alive, use a `null_deleter` as explained <> and <>. +If `X` is supposed to always live on the heap, and be managed by a `shared_ptr`, use a static factory function: + +``` +class X +{ +private: + + X() { ... } + +public: + + static shared_ptr create() + { + shared_ptr 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 under the assumption that `this` is already managed by a `shared_ptr`. +The transformations <> cannot be applied. + +A typical example: + +``` +class X +{ +public: + + virtual void f() = 0; + +protected: + + ~X() {} +}; + +class Y +{ +public: + + virtual shared_ptr getX() = 0; + +protected: + + ~Y() {} +}; + +// -- + +class impl: public X, public Y +{ +public: + + impl() { ... } + + virtual void f() { ... } + + virtual shared_ptr getX() + { + shared_ptr px(???); + return px; + } +}; +``` + +The solution is to keep a weak pointer to `this` as a member in `impl`: + +``` +class impl: public X, public Y +{ +private: + + weak_ptr weak_this; + + impl(impl const &); + impl & operator=(impl const &); + + impl() { ... } + +public: + + static shared_ptr create() + { + shared_ptr pi(new impl); + pi->weak_this = pi; + return pi; + } + + virtual void f() { ... } + + virtual shared_ptr getX() + { + shared_ptr px(weak_this); + return px; + } +}; +``` + +The library now includes a helper class template `<>` that can be used to encapsulate the solution: + +``` +class impl: public X, public Y, public enable_shared_from_this +{ +public: + + impl(impl const &); + impl & operator=(impl const &); + +public: + + virtual void f() { ... } + + virtual shared_ptr getX() + { + return shared_from_this(); + } +} +``` + +Note that you no longer need to manually initialize the `weak_ptr` member in `enable_shared_from_this`. Constructing a `shared_ptr` to `impl` takes care of that. + +## Using shared_ptr as a smart counted handle + +Some library interfaces use opaque handles, a variation of the <> described above. An example: + +``` +typedef void * HANDLE; + +HANDLE CreateProcess(); +void CloseHandle(HANDLE); +``` + +Instead of a raw pointer, it is possible to use `shared_ptr` as the handle and get reference counting and automatic resource management for free: + +``` +typedef shared_ptr handle; + +handle createProcess() +{ + shared_ptr pv(CreateProcess(), CloseHandle); + return pv; +} +``` + +## Using shared_ptr to execute code on block exit + +`shared_ptr` can automatically execute cleanup code when control leaves a scope. + +* Executing `f(p)`, where `p` is a pointer: ++ +``` +shared_ptr guard(p, f); +``` + +* Executing arbitrary code: `f(x, y)`: ++ +``` +shared_ptr guard(static_cast(0), bind(f, x, y)); +``` + +## Using shared_ptr to hold an arbitrary object + +`shared_ptr` can act as a generic object pointer similar to `void*`. When a `shared_ptr` instance constructed as: + + shared_ptr 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` can later be cast back to the correct type by using `<>`. + +## Associating arbitrary data with heterogeneous `shared_ptr` instances + +`shared_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, Data> userData; +// or std::map, Data> userData; to not affect the lifetime + +shared_ptr px(new X); +shared_ptr pi(new int(3)); + +userData[px] = 42; +userData[pi] = 91; +``` + +## Using `shared_ptr` as a `CopyConstructible` mutex lock + +Sometimes it's necessary to return a mutex lock from a function, and a noncopyable lock cannot be returned by value. It is possible to use `shared_ptr` as a mutex lock: + +``` +class mutex +{ +public: + + void lock(); + void unlock(); +}; + +shared_ptr lock(mutex & m) +{ + m.lock(); + return shared_ptr(&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 pv; + +public: + + template 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`’s ability to hide type information. + +## Using shared_ptr to wrap member function calls + +`shared_ptr` implements the ownership semantics required from the `Wrap/CallProxy` scheme described in Bjarne Stroustrup's article +"Wrapping C++ Member Function Calls" (available online at http://www.stroustrup.com/wrapper.pdf). An implementation is given below: + +``` +template class pointer +{ +private: + + T * p_; + +public: + + explicit pointer(T * p): p_(p) + { + } + + shared_ptr operator->() const + { + p_->prefix(); + return shared_ptr(p_, mem_fn(&T::suffix)); + } +}; + +class X +{ +private: + + void prefix(); + void suffix(); + friend class pointer; + +public: + + void f(); + void g(); +}; + +int main() +{ + X x; + + pointer 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 px; + +public: + + void f() + { + px.reset(); + } +}; +``` + +The solution is to postpone the potential deallocation by moving `px` to a dedicated free list that can be periodically emptied when performance and response times are not an issue: + +``` +vector< shared_ptr > free_list; + +class Y +{ + shared_ptr px; + +public: + + void f() + { + free_list.push_back(px); + px.reset(); + } +}; + +// periodically invoke free_list.clear() when convenient +``` + +Another variation is to move the free list logic to the construction point by using a delayed deleter: + +``` +struct delayed_deleter +{ + template void operator()(T * p) + { + try + { + shared_ptr pv(p); + free_list.push_back(pv); + } + catch(...) + { + } + } +}; +``` + +[#techniques_weak_without_shared] +## 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 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 get_weak_ptr() const { return this_; } +}; +``` + +When the object's lifetime ends, `X::this_` will be destroyed, and all weak pointers will automatically expire.