From 8f0bdd48f8cf61c965a30424b7ffb6a5983bedbb Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Wed, 12 Feb 2003 19:11:52 +0000 Subject: [PATCH] Minor edits. [SVN r17335] --- sp_techniques.html | 145 ++++++++++++++++----------------------------- 1 file changed, 51 insertions(+), 94 deletions(-) diff --git a/sp_techniques.html b/sp_techniques.html index 0aad415..eb6dcfd 100644 --- a/sp_techniques.html +++ b/sp_techniques.html @@ -7,8 +7,7 @@

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

-

- Using incomplete classes for implementation hiding
+

Using incomplete classes for implementation hiding
The "Pimpl" idiom
Using abstract classes for implementation hiding
Preventing delete px.get()
@@ -42,8 +41,7 @@

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

It is possible to express the above interface using 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);
 		

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. 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 by shared_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 array

A shared_ptr can be used to hold a pointer to an array allocated with new[]:

-
-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, where CreateX might allocate X from its own private heap, ~X may be inaccessible, or X may be incomplete:

-
-X * CreateX();
+		
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<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 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<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 and intrusive_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 a shared_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, using get_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 another shared_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 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<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, but X has an embedded - reference count, use make_shared_from_intrusive - described above. Or, if it's known that the shared_ptr created in - f will never outlive the object, use a null deleter.

+

If f cannot be changed, but X uses intrusive counting, + use make_shared_from_intrusive described + above. Or, if it's known that the shared_ptr created in f + will never outlive the object, use a null deleter.

Obtaining a shared_ptr (weak_ptr) to this in a constructor

[...]

-
-class X
+		
class X
 {
 public:
 
     X()
     {
-        shared_ptr<X> this_(???);
+        shared_ptr<X> this_(???);
     }
 };
 
@@ -418,8 +396,7 @@ public: 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
+		
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), where p 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 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
+		
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_deleter
 

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