473,387 Members | 3,801 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes and contribute your articles to a community of 473,387 developers and data experts.

Handle Classes: The Smart Pointer

weaknessforcats
9,208 Expert Mod 8TB
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.
May 23 '07 #1
2 35438
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.
Mar 2 '08 #2
weaknessforcats
9,208 Expert Mod 8TB
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.
Mar 4 '08 #3

Sign in to post your reply or Sign up for a free account.

Similar topics

7
by: Tony Johansson | last post by:
Hello!! Assume I have a handle body pattern with classes called Handle and Body. In the Body class I store one int value for example 7 or some other integer value. In the Handle class I have...
2
by: Indiana Epilepsy and Child Neurology | last post by:
Before asking this questions I've spent literally _years_ reading (Meyer, Stroustrup, Holub), googling, asking more general design questions, and just plain thinking about it. I am truly unable to...
14
by: Howard | last post by:
Hi, I recently had a problem where I decided to store objects in a vector. (Previously, I had always stored pointers in vectors). Well, naturally, when storing an object in a vector, using...
7
by: caxmester | last post by:
Ok this shouldn't be nearly this hard, but i've checked EVERYWHERE from msdn to scouring google & I can't figure it out. I basically have a vector of pointers to "aaWord" objects, each of which...
8
by: Axter | last post by:
I normally use a program call Doxygen to document my source code.(http://www.stack.nl/~dimitri/doxygen) This method works great for small and medium size projects, and you can get good...
5
by: mancomb | last post by:
Hi, I'm curious to the syntax of calling member functions through pointers of classes returned through the -operator. For example (excuse the crude incomplete code); Here are the classes: ...
33
by: Ney André de Mello Zunino | last post by:
Hello. I have written a simple reference-counting smart pointer class template called RefCountPtr<T>. It works in conjunction with another class, ReferenceCountable, which is responsible for the...
7
by: Shraddha | last post by:
What is exactly a handle to an object????Is it same as reference....
8
by: Oliver Graeser | last post by:
Hi All, I'm coming from Java to C++ and this is one of the very last problems I have so far... In Java, if I have, say, a class SISNode that extends NetworkNode, I can have a function that...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.