Handle Classes: The Smart Pointer

来源:互联网 发布:淘宝网店新店怎么运营 编辑:程序博客网 时间:2024/06/09 18:55

Handle Classes

Handle classes, also called Envelope or Cheshire Cat classes, are part of the Bridge design pattern. The objective of the Bridge pattern is to separate the abstraction from the implementation so the two can vary independently.

Handle classes usually contain a pointer to the object implementation. The Handle object is used rather than the implemented object. This leaves the implemented object free to change without affecting the Handle object. This is exactly what happens with pointers. The object changes but the address in the pointer does not.

One of the problems with passing pointers is that you never know if it's safe to delete the pointer. If the pointer is a stack pointer and you delete, you crash. If the pointer is to a heap object and you delete, the object pointed at is deleted and this is just fine unless there is another pointer somewhere in the program that still points to the object you just deleted. If there is and you use that pointer, you crash. If you play it safe and never delete you die a death by a thousand memory leaks.

Objects of Handle classes are used like pointers even though they are objects. This is accomplished by overloading the dereference operator (*) and the indirection operator (->). Because these are objects, they can contain data beyond the pointer to the implementation. Like maybe, a count of how may other handles also point to the same object.

An internal count of the number of handles containing a pointer to the same object is called a reference count. A handle with an internal reference count is called a reference counted handle.

The rule on reference counting is that when you are to make a function call that requires a pointer, you increment the count for the number of copies of that pointer. When then function you call is about to return, it decrements the count. If the count is now zero, the function has the last copy of the pointer and it is now safe to delete the object pointed at by the pointer.

Here is an example of a handle:

Expand|Select|Wrap|Line Numbers
  1. #include <iostream>
  2. using namespace std;
  3.  
  4. //Handle classes or "Cheshire Cat" classes
  5. //separate interface from implementation
  6. //by using an abstract type.
  7.  
  8. //The implementation
  9. class MyClass
  10. {
  11.  
  12.     private:
  13.         int adata;
  14.         int bdata;
  15.     public:
  16.         MyClass(int a, int b) : adata(a), bdata(b) { }
  17.         void Methoda() {cout << "Methoda() " << adata << endl;}    
  18.         void Methodb() {cout << "Methodb() " << bdata << endl;}
  19. };
  20.  
  21. //The interface
  22. class HandleToMyClass
  23. {
  24.     private:
  25.         MyClass* imp;
  26.  
  27.  
  28.     public:
  29.  
  30.         HandleToMyClass(int x, int y) : imp(new MyClass(x,y)) {  }
  31.  
  32.         MyClass* operator->() {return imp;}
  33.  
  34.  
  35. };
  36.  
  37. int main()
  38. {    
  39.     /////////////////////////////////////////////////////////////////////////
  40.     //The Handle class manages its own instnace of the Hidden class
  41.     //
  42.     HandleToMyClass hobj(10,20);
  43.  
  44.     hobj->Methoda();    //Use hobj instead of passing copies of MyClass around
  45.     hobj->Methodb();
  46.  
  47.     ////////////////////////////////////////////////////////////////////////
  48.  
  49.  
  50.     return 0;
  51. }
  52.  
You can easily see that hobj is an object but it is used in main() as if it were a pointer.

Reference Counted Handles

A reference count keeps track of the number of other handle objects pointing at the same implementation object. This is done by increasing the count in the constructor and decreasing the count in the destructor. When the count goes to zero inside the handle destructor, then it is safe to delete the implementation object. The count itself is also on the heap so it can travel from handle to handle.

In the example below you can see how the count is managed. Note in the handle assignment operator how the count of the LVAL object is decreased and the count of the RVAL object is increased.

Also added to this example is the use of a create function to create the handle. You want to avoid creating an object and then creating a handle and then putting the object pointer inside the handle. The reason to avoid this is: It's not safe. You, at some point, will omit one of the steps or decide to just use the implementation object without bothering about the handle. The entire security net provided by handle fails at this point.

Expand|Select|Wrap|Line Numbers
  1.  
  2.  
  3. #include <iostream>
  4. using namespace std;
  5.  
  6. //A reference counting handle keeps track of the number of existing copies
  7. //of the handle. The object managed by the handle is not destroyed until
  8. //the destructor of the last handle object is called.
  9.  
  10.  
  11. class MyClass
  12. {
  13.  
  14.     private:
  15.         int adata;
  16.         int bdata;
  17.     public:
  18.         MyClass(int a, int b) : adata(a), bdata(b) { }
  19.         ~MyClass() {cout << "Egad! The MyClass object is gone!" << endl;}
  20.         void seta(int in) {adata = in;}
  21.         void setb(int in) {bdata = in;}
  22.  
  23.         //Inserter
  24.         friend ostream& operator<<(ostream& os, const MyClass& rhs);
  25. };
  26. //MyClass Inserter
  27. ostream& operator<<(ostream& os, const MyClass& rhs)
  28. {
  29.     os << "adata: " << rhs.adata << " bdata: " << rhs.bdata;
  30.     return os;
  31. }
  32.  
  33. class HandleToMyClass
  34. {
  35.     private:
  36.         MyClass* imp;
  37.         int* RefCount;
  38.  
  39.     public:
  40.  
  41.         HandleToMyClass() : imp(0), RefCount(new int(0)) { }
  42.  
  43.         HandleToMyClass(int x, int y) : imp(new MyClass(x,y)),
  44.                                         RefCount(new int(1)) {  }
  45.  
  46.         //Destructor deletes managed object when reference count is zero
  47.         ~HandleToMyClass();
  48.  
  49.         //Copy constructor increments the reference count
  50.         HandleToMyClass(const HandleToMyClass& rhs);
  51.  
  52.         //Assignment operator decrements lhs reference count and
  53.         //increments rhs reference count
  54.         HandleToMyClass& operator=(const HandleToMyClass& rhs);
  55.  
  56.                //Support using the handle object as a pointer
  57.         MyClass* operator->() {return imp;}
  58.                 MyClass& operator*() {return *imp;}
  59. };
  60.  
  61.  
  62.  
  63. //Destructor deletes managed object when reference count is zero
  64.     HandleToMyClass::~HandleToMyClass()
  65. {
  66.     //RefCount can be zero if this handle was never assigned an object.
  67.     //In this case subtracting can cause a negative value
  68.  
  69.     if (--(*RefCount) <= 0)    //not thread-safe
  70.     {
  71.             delete imp;
  72.             delete RefCount;
  73.             imp = 0;
  74.             RefCount = 0;
  75.  
  76.     }
  77.  
  78. }
  79.  
  80. //Copy constructor increments the reference count
  81.     HandleToMyClass::HandleToMyClass(const HandleToMyClass& rhs)
  82.     : imp(rhs.imp), RefCount(rhs.RefCount)    //not thread-safe
  83. {
  84.     ++(*RefCount);
  85. }
  86.  
  87.  
  88. //Assignment operator decrements lhs reference count and
  89. //increments rhs reference count
  90. HandleToMyClass& HandleToMyClass::operator=(const HandleToMyClass& rhs)
  91. {
  92.     if (this == &rhs) return *this; //no assignment to self
  93.  
  94.     //Delete our current implementation (LVAL)
  95.     HandleToMyClass::~HandleToMyClass();
  96.  
  97.     //This is our new implementation (RVAL):
  98.     imp = rhs.imp;
  99.     RefCount = rhs.RefCount;    //not thread-safe
  100.     ++(*RefCount);
  101.  
  102.     return *this;
  103. }
  104. //
  105. //Use a create function to create both the MyClass object and its handle
  106. //
  107. HandleToMyClass CreateMyClassHandle(int a, int b)
  108. {
  109.     return HandleToMyClass(10,20);
  110. }
  111.  
  112. void Process(HandleToMyClass in)
  113. {
  114.         //Silly stuff to exercise the handle
  115.     HandleToMyClass x;
  116.  
  117.     x = in;
  118.  
  119.     HandleToMyClass y(x);
  120.  
  121.     y->seta(30);   //Use handle object as a pointer
  122. }
  123.  
  124. int main()
  125. {    
  126.     //Create the MyClass object and the handle
  127.     //
  128.     HandleToMyClass hobj = CreateMyClassHandle(10,20);
  129.  
  130.     cout << *hobj << endl;
  131.  
  132.     Process(hobj);
  133.  
  134.     cout << *hobj << endl;
  135.  
  136.     ////////////////////////////////////////////////////////////////////////
  137.  
  138.  
  139.     return 0;
  140. }
  141.  
Reference Counted Handles as a Template

A Handle is a prime candidate for a template since all Handles behave the same and only the type if the implementation object varies.

Shown below is the Handle as a template as you would see it in a header file. It was prepared from the HandleToMyClass used in the previous example.

You should be able to use this Handle template in your own code.

Expand|Select|Wrap|Line Numbers
  1. #ifndef  HANDLETEMPLATEH
  2. #define HANDLETEMPLATEH
  3.  
  4. //A reference counting handle keeps track of the number of existing copies
  5. //of the handle. The object managed by the handle is not destroyed until
  6. //the destructor of the last handle object is called.
  7.  
  8. //Converting the handle to a template:
  9. template<class T>
  10. class Handle
  11. {
  12.     private:
  13.         T* imp;
  14.         int* RefCount;
  15.  
  16.     public:
  17.  
  18.         Handle() : imp(0), RefCount(new int(0)) { }
  19.  
  20.         Handle(T* in) : imp(in), RefCount(new int(1)) {  }
  21.  
  22.         //Destructor deletes managed object when reference count is zero
  23.         ~Handle();
  24.  
  25.         //Copy constructor increments the reference count
  26.         Handle(const Handle<T>& rhs);
  27.  
  28.         //Assignment operator decrements lhs reference count and
  29.         //increments rhs reference count
  30.         Handle<T> operator=(const Handle<T>& rhs);
  31.  
  32.         //Support using the handle object as a pointer
  33.         T* operator->() {return imp;}
  34.         T& operator*() {return *imp;}
  35. };
  36.  
  37. //Destructor deletes managed object when reference count is zero
  38. template<class T>
  39.     Handle<T>::~Handle()
  40. {
  41.     //RefCount can be zero if this handle was never assigned an object.
  42.     //In this case subtracting can cause a negative value
  43.     if (--(*RefCount) <= 0)    //not thread-safe    {
  44.                 delete imp;
  45.                 delete RefCount;
  46.                 imp = 0;
  47.                 RefCount = 0;
  48.  
  49.     }
  50. }
  51.  
  52. //Copy constructor increments the reference count
  53. template<class T>
  54.     Handle<T>::Handle(const Handle<T>& rhs)
  55.     : imp(rhs.imp), RefCount(rhs.RefCount)    //not thread-safe
  56. {
  57.     ++(*RefCount);
  58. }
  59.  
  60.  
  61. //Assignment operator decrements lhs reference count and
  62. //increments rhs reference count
  63. template<class T>
  64. Handle<T> Handle<T>::operator=(const Handle<T>& rhs)
  65. {
  66.     if (this == &rhs) return *this; //no assignment to self
  67.  
  68.     //Delete our current implementation (LVAL)
  69.     Handle::~Handle();
  70.  
  71.     //This is our new implementation (RVAL):
  72.     imp = rhs.imp;
  73.     RefCount = rhs.RefCount;    //not thread-safe
  74.     ++(*RefCount);
  75.  
  76.     return *this;
  77. }
  78.  
  79.  
  80.  
  81. #endif  //end of #ifndef HANDLEREFCOUNTTEMPLATEH
  82.  
Using a Reference Counted Handle

This example shows the reference counted handle in use with MyClass from the first example in this article.
Expand|Select|Wrap|Line Numbers
  1. void Process(Handle<MyClass> in)
  2. {
  3.         //Silly stuff to exercise the handle
  4.     Handle<MyClass> x;
  5.  
  6.     x = in;
  7.  
  8.     Handle<MyClass> y(x);
  9.  
  10.     y->seta(30);   //Use handle object as a pointer
  11. }
  12.  
  13. int main()
  14. {    
  15.     //Create the MyClass object and the handle
  16.     //
  17.     Handle<MyClass> hobj = CreateMyClassHandle(10,20);
  18.  
  19.     cout << *hobj << endl;
  20.  
  21.     Process(hobj);
  22.  
  23.     cout << *hobj << endl;
  24.  
  25.     //
  26.     //Unused Handle
  27.     //
  28.     Handle<MyClass> unused;
  29.  
  30.     ////////////////////////////////////////////////////////////////////////
  31.  
  32.  
  33.     return 0;
  34. }
  35.  
Create Functions Using the Handle Template

You create a handle by using a create function. Let's assume you have a Person class:

Expand|Select|Wrap|Line Numbers
  1. class Person
  2. {
  3.    private:
  4.        string name;
  5.        string address;
  6.    public:
  7.         Person(string name, string address);
  8.         etc...
  9. };
  10.  
You would write a CreatePersonHandle function as follows:

Expand|Select|Wrap|Line Numbers
  1. Handle<Person> hp = CreatePersonHandle("John Smith", "123 Fox St  Anytown USA");
  2.  
where the create function is:

Expand|Select|Wrap|Line Numbers
  1. Handle<Person> CreatePersonHandle(string name, string address)
  2. {
  3.      Person* temp = new Person(name, address);
  4.      Handle<Person> rval(temp);
  5.      return rval;
  6. }
  7.  
Using the Reference Counted Handle with an STL Container

From here on you use the Handle<Person> as a Person*. If you needed a vector<Person> you would now create a vector<Handle<Person> >:

Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     Handle<Person? h1 = CreatePersonHandle("John Smith", "123 Fox St  Anytown USA");
  4.     Handle<Person? h2 = CreatePersonHandle("Sue Collins", "James Boulevard  ACity USA");
  5.  
  6.     vector<Handle<Person> > database;
  7.     database.push_back(h1);
  8.     database.push_back(h2);
  9.  
  10.     cout << *database[0] << endl;
  11.     cout << *database[1] << endl;
  12.  
  13. }
  14.  
The advantage here is the vector is a vector of handles. Each handle has only two pointers: One for the implementation object and one for the count. Making a copy just requires making copies of the pointers rather than the implementation object itself. This will make for a much smaller vector and when elements are deleted, only the two pointers are deleted unless that was the last handle at which time the implementation object is also deleted.

Containers tend to move their contents around. When copies are made, the copy constructor of the objects in the container is called. Almost certainly this will take more time than to copy a Handle. Also, containers, like vector, do not readily release memory. They tend to hoard it to avoid more memory allocations if items are added in the future. A Handle just causes the container to hoard the sizeof two pointers.

Using Handles as Overloaded Operators

Often a sort or some other process is required that involves comparing objects. In these cases, if you have a container of handles, you will be comparing two handles. This is not what you want. You need to compare the objects pointed at by the handles.

First, you do not add operator functions to the Handle template.

Instead, you write a compare function that has Handle arguments and returns the correct bool value. Using the Person class, above, as an example you could:

Expand|Select|Wrap|Line Numbers
  1. bool operator<(Handle<Person> left, Handle<Person> right)
  2. {
  3.      if (*left < *right) return true;      //calls Person::operator<
  4.      return false;
  5. }
  6.  
If the Person class does not have an operator<, then you would use other Person functions to accomplish the comparison:

Expand|Select|Wrap|Line Numbers
  1. bool operator<(Handle<Person> left, Handle<Person> right)
  2. {
  3.      if (left->GetName() < right->GetName()) return true;    
  4.      return false;
  5. }
  6.  

Copyright 2007 Buchmiller Technical Associates North Bend WA USA

Revision History:
Feb 6, 2008: Corrected reference count error in Handle destructor.

Share this article on:

Twitter Facebook StumbleUpon Digg

Last edited by weaknessforcats; Feb 6 '08 at 02:38 PM.Reason: Fixed bug: Changed handle destructor to: if (--(*RefCount) <= 0)//not thread-safe


Peterwkc
 
Join Date: Apr 2007
Location: Malaysia
Posts: 55
#2  Mar 2 '08

re: Handle Classes: The Smart Pointer


Nice article.

By the way, why u create function create handle rather than inside the constructor.

Another problem is

Expand|Select|Wrap|Line Numbers
  1. template<class T>
  2. Handle<T> Handle<T>::operator=(const Handle<T>& rhs)
  3. {
  4.     if (this == &rhs) return *this; //no assignment to self
  5.  
  6.     //Delete our current implementation (LVAL)
  7.     Handle::~Handle();
  8.  
  9.     //This is our new implementation (RVAL):
  10.     imp = rhs.imp;
  11.     RefCount = rhs.RefCount;    //not thread-safe
  12.     ++(*RefCount);
  13.  
  14.     return *this;
  15. }
Why inside assignment operator, why we need to always delete first before assign ?

a = b;

Do we delete a or b ?

Sorry for my stupidity.

Thanks for your help.
weaknessforcats
E
M
M
 
Join Date: Mar 2007
Location: North Bend Washington USA
Posts: 6,171
#3  Mar 4 '08

re: Handle Classes: The Smart Pointer


I use a create function to create, initialize and return a handle to your object. I did this to be sure the Handle was created on the heap.

As to deleting before assigning, think about it. If you are going to replace the current contents of an object with new contents, then you need to delete the current contents to avoid a memory leak. In your example of a = b, it is the contents of a that need to be deleted. Deleting the contents of b would delete the data you want to assign to a.

原创粉丝点击