From 8f0bdd48f8cf61c965a30424b7ffb6a5983bedbb Mon Sep 17 00:00:00 2001
From: Peter Dimov
- Using incomplete classes for implementation hiding 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: It is possible to express the above interface using A C++ 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.
- Smart
pointer programming techniques
+
The "Pimpl" idiom
Using abstract classes for implementation hiding
Preventing delete px.get()
@@ -42,8 +41,7 @@
Using incomplete classes for implementation hiding
-class FILE;
+
class FILE;
FILE * fopen(char const * name, char const * mode);
void fread(FILE * f, void * data, size_t size);
@@ -51,8 +49,7 @@ void fclose(FILE * f);
shared_ptr
,
eliminating the need to manually call fclose
:
-class FILE;
+
class FILE;
shared_ptr<FILE> fopen(char const * name, char const * mode);
void fread(shared_ptr<FILE> f, void * data, size_t size);
@@ -65,8 +62,7 @@ void fread(shared_ptr<FILE> f, void * data, size_t size);
shared_ptr
can be used to implement a "Pimpl":
-// file.hpp:
+
// file.hpp:
class file
{
@@ -84,8 +80,7 @@ public:
void read(void * data, size_t size);
};
-
-// file.cpp:
+
// file.cpp:
#include "file.hpp"
@@ -124,8 +119,7 @@ void file::read(void * data, size_t size)
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: +// X.hpp: class X { @@ -141,8 +135,7 @@ protected: shared_ptr<X> createX();---- X.cpp: +-- X.cpp: class X_impl: public X { @@ -181,8 +174,7 @@ shared_ptr<X> createX() being managed byshared_ptr
. The previous technique showed one possible approach, using a protected destructor. Another alternative is to use a private deleter: --class X +class X { private: @@ -210,8 +202,7 @@ public:Using a
shared_ptr
to hold a pointer to an arrayA
-shared_ptr
can be used to hold a pointer to an array allocated withnew[]
:-shared_ptr<X> px(new X[1], checked_array_deleter<X>()); +shared_ptr<X> px(new X[1], checked_array_deleter<X>());Note, however, that
-shared_array
is often preferable, if this is an option. It has an array-specific interface, @@ -224,15 +215,13 @@ shared_ptr<X> px(new X[1], checke to encapsulate allocation details. As an example, consider this interface, whereCreateX
might allocateX
from its own private heap,~X
may be inaccessible, orX
may be incomplete:-X * CreateX(); +X * CreateX(); void DestroyX(X *);The only way to reliably destroy a pointer returned by
CreateX
is to callDestroyX
.Here is how a
-shared_ptr
-based wrapper may look like:-shared_ptr<X> createX() +shared_ptr<X> createX() { shared_ptr<X> px(CreateX(), DestroyX); return px; @@ -246,14 +235,12 @@ shared_ptr<X> createX() existing object, so that theshared_ptr
does not attempt to destroy the object when there are no more references left. As an example, the factory function: --shared_ptr<X> createX(); +shared_ptr<X> 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 +struct null_deleter { void operator()(void const *) const { @@ -274,11 +261,10 @@ shared_ptr<X> createX() 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<IWhatever> make_shared_from_COM(IWhatever * p) +shared_ptr<IWhatever> make_shared_from_COM(IWhatever * p) { p->AddRef(); - shared_ptr<IWhatever> pw(p, mem_fn(&IWhatever::Release)); + shared_ptr<IWhatever> pw(p, mem_fn(&IWhatever::Release)); return pw; }@@ -292,8 +278,7 @@ shared_ptr<IWhatever> make_shared_from_COM(IWhatever * p)This is a generalization of the above technique. The example assumes that the object implements the two functions required by
-intrusive_ptr
,intrusive_ptr_add_ref
andintrusive_ptr_release
:-template<class T> struct intrusive_deleter +template<class T> struct intrusive_deleter { void operator()(T * p) { @@ -316,8 +301,7 @@ shared_ptr<X> make_shared_from_intrusive(X * p) counted or linked smart pointer.It is possible to exploit
-shared_ptr
's custom deleter feature to wrap this existing smart pointer behind ashared_ptr
facade:-template<class P> struct smart_pointer_deleter +template<class P> struct smart_pointer_deleter { private: @@ -356,8 +340,7 @@ shared_ptr<X> make_shared_from_another(another_ptr<X> qx)Another twist is that it is possible, given the above
-shared_ptr
instance, to recover the original smart pointer, usingget_deleter
:-void extract_another_from_shared(shared_ptr<X> px) +void extract_another_from_shared(shared_ptr<X> px) { typedef smart_pointer_deleter< another_ptr<X> > deleter; @@ -375,42 +358,37 @@ void extract_another_from_shared(shared_ptr<X> px)Sometimes it is necessary to obtain a
-shared_ptr
given a raw pointer to an object that is already managed by anothershared_ptr
instance. Example:-void f(X * p) +void f(X * p) { - shared_ptr<X> px(???); + shared_ptr<X> px(???); }Inside
f
, we'd like to create ashared_ptr
to*p
.In the general case, this problem has no solution. One approach is to modify
-f
to take ashared_ptr
, if possible:-void f(shared_ptr<X> px); +void f(shared_ptr<X> px);The same transformation can be used for nonvirtual member functions, to convert the implicit
-this
:-void X::f(int m); +void X::f(int m);would become a free function with a
-shared_ptr
first argument:-void f(shared_ptr<X> this_, int m); +void f(shared_ptr<X> this_, int m);-If
+f
cannot be changed, butX
has an embedded - reference count, usemake_shared_from_intrusive
- described above. Or, if it's known that theshared_ptr
created in- f
will never outlive the object, use a null deleter.If
f
cannot be changed, butX
uses intrusive counting, + usemake_shared_from_intrusive
described + above. Or, if it's known that theshared_ptr
created inf
+ will never outlive the object, use a null deleter.Obtaining a
shared_ptr
(weak_ptr
) tothis
in a constructor[...]
--class X +class X { public: X() { - shared_ptr<X> this_(???); + shared_ptr<X> this_(???); } };@@ -418,8 +396,7 @@ public: storage, andthis_
doesn't need to keep the object alive, use anull_deleter
. IfX
is supposed to always live on the heap, and be managed by ashared_ptr
, use:] --class X +class X { private: @@ -439,8 +416,7 @@ public:[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 +class X { public: @@ -474,14 +450,13 @@ public: virtual shared_ptr<X> getX() { - shared_ptr<X> px(???); + shared_ptr<X> px(???); return px; } };[Solution:]
--class impl: public X, public Y +class impl: public X, public Y { private: @@ -513,14 +488,12 @@ public:[Future support planned,
impl: public enable_shared_from_this<impl>
.]Using
shared_ptr
as a smart counted handle[Win32 API allusion]
--typedef void * HANDLE; +typedef void * HANDLE; HANDLE CreateProcess(); void CloseHandle(HANDLE);[Quick wrapper]
--typedef shared_ptr<void> handle; +typedef shared_ptr<void> handle; handle createProcess() { @@ -529,8 +502,7 @@ handle createProcess() }[Better, typesafe:]
--class handle +class handle { private: @@ -544,18 +516,15 @@ public:Using
shared_ptr
to execute code on block exit[1. Executing
-f(p)
, wherep
is a pointer:]- shared_ptr<void> guard(p, f); +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> guard(static_cast<void*>(0), bind(f, x, y));Using
shared_ptr<void>
to hold an arbitrary object[...]
-- shared_ptr<void> pv(new X); +shared_ptr<void> pv(new X);[Will correctly call
~X
.][Can be used to strip type information:
shared_ptr<X>
->@@ -563,8 +532,7 @@ public:
Associating arbitrary data with heterogeneous
shared_ptr
instances[...]
--typedef int Data; +typedef int Data; std::map< shared_ptr<void>, Data > userData; // or std::map< weak_ptr<void>, Data > userData; to not affect the lifetime @@ -577,8 +545,7 @@ userData[pi] = 91;Post-constructors and pre-destructors
[...]
--class X +class X { public: @@ -607,8 +574,7 @@ public: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 +class mutex { public: @@ -623,8 +589,7 @@ shared_ptr<mutex> lock(mutex & m) }[Or to encapsulate it in a dedicated class:]
--class shared_lock +class shared_lock { private: @@ -636,15 +601,13 @@ public: };[Usage:]
-- shared_lock lock(m); +shared_lock lock(m);[Note that
shared_lock
is not templated on the mutex type, thanks toshared_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 +template<class T> class pointer { private: @@ -690,8 +653,7 @@ int main()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 X; // ~X is expensive class Y { @@ -706,8 +668,7 @@ public: };[Solution 1]
--vector< shared_ptr<void> > free_list; +vector< shared_ptr<void> > free_list; class Y { @@ -725,8 +686,7 @@ public: // periodically invoke free_list.clear() when convenient[Solution 2, as above, but use a delayed deleter]
--struct delayed_deleter +struct delayed_deleter { template<class T> void operator()(T * p) { @@ -743,8 +703,7 @@ struct delayed_deleterWeak pointers to objects not managed by a
shared_ptr
Make the object hold a
-shared_ptr
to itself, using anull_deleter
:-class X +class X { private: @@ -776,10 +735,8 @@ public: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 +
$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.