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
- #include <iostream>
- using namespace std;
- //Handle classes or "Cheshire Cat" classes
- //separate interface from implementation
- //by using an abstract type.
- //The implementation
- class MyClass
- {
- private:
- int adata;
- int bdata;
- public:
- MyClass(int a, int b) : adata(a), bdata(b) { }
- void Methoda() {cout << "Methoda() " << adata << endl;}
- void Methodb() {cout << "Methodb() " << bdata << endl;}
- };
- //The interface
- class HandleToMyClass
- {
- private:
- MyClass* imp;
- public:
- HandleToMyClass(int x, int y) : imp(new MyClass(x,y)) { }
- MyClass* operator->() {return imp;}
- };
- int main()
- {
- /////////////////////////////////////////////////////////////////////////
- //The Handle class manages its own instnace of the Hidden class
- //
- HandleToMyClass hobj(10,20);
- hobj->Methoda(); //Use hobj instead of passing copies of MyClass around
- hobj->Methodb();
- ////////////////////////////////////////////////////////////////////////
- return 0;
- }
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
- #include <iostream>
- using namespace std;
- //A reference counting handle keeps track of the number of existing copies
- //of the handle. The object managed by the handle is not destroyed until
- //the destructor of the last handle object is called.
- class MyClass
- {
- private:
- int adata;
- int bdata;
- public:
- MyClass(int a, int b) : adata(a), bdata(b) { }
- ~MyClass() {cout << "Egad! The MyClass object is gone!" << endl;}
- void seta(int in) {adata = in;}
- void setb(int in) {bdata = in;}
- //Inserter
- friend ostream& operator<<(ostream& os, const MyClass& rhs);
- };
- //MyClass Inserter
- ostream& operator<<(ostream& os, const MyClass& rhs)
- {
- os << "adata: " << rhs.adata << " bdata: " << rhs.bdata;
- return os;
- }
- class HandleToMyClass
- {
- private:
- MyClass* imp;
- int* RefCount;
- public:
- HandleToMyClass() : imp(0), RefCount(new int(0)) { }
- HandleToMyClass(int x, int y) : imp(new MyClass(x,y)),
- RefCount(new int(1)) { }
- //Destructor deletes managed object when reference count is zero
- ~HandleToMyClass();
- //Copy constructor increments the reference count
- HandleToMyClass(const HandleToMyClass& rhs);
- //Assignment operator decrements lhs reference count and
- //increments rhs reference count
- HandleToMyClass& operator=(const HandleToMyClass& rhs);
- //Support using the handle object as a pointer
- MyClass* operator->() {return imp;}
- MyClass& operator*() {return *imp;}
- };
- //Destructor deletes managed object when reference count is zero
- HandleToMyClass::~HandleToMyClass()
- {
- //RefCount can be zero if this handle was never assigned an object.
- //In this case subtracting can cause a negative value
- if (--(*RefCount) <= 0) //not thread-safe
- {
- delete imp;
- delete RefCount;
- imp = 0;
- RefCount = 0;
- }
- }
- //Copy constructor increments the reference count
- HandleToMyClass::HandleToMyClass(const HandleToMyClass& rhs)
- : imp(rhs.imp), RefCount(rhs.RefCount) //not thread-safe
- {
- ++(*RefCount);
- }
- //Assignment operator decrements lhs reference count and
- //increments rhs reference count
- HandleToMyClass& HandleToMyClass::operator=(const HandleToMyClass& rhs)
- {
- if (this == &rhs) return *this; //no assignment to self
- //Delete our current implementation (LVAL)
- HandleToMyClass::~HandleToMyClass();
- //This is our new implementation (RVAL):
- imp = rhs.imp;
- RefCount = rhs.RefCount; //not thread-safe
- ++(*RefCount);
- return *this;
- }
- //
- //Use a create function to create both the MyClass object and its handle
- //
- HandleToMyClass CreateMyClassHandle(int a, int b)
- {
- return HandleToMyClass(10,20);
- }
- void Process(HandleToMyClass in)
- {
- //Silly stuff to exercise the handle
- HandleToMyClass x;
- x = in;
- HandleToMyClass y(x);
- y->seta(30); //Use handle object as a pointer
- }
- int main()
- {
- //Create the MyClass object and the handle
- //
- HandleToMyClass hobj = CreateMyClassHandle(10,20);
- cout << *hobj << endl;
- Process(hobj);
- cout << *hobj << endl;
- ////////////////////////////////////////////////////////////////////////
- return 0;
- }
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
- #ifndef HANDLETEMPLATEH
- #define HANDLETEMPLATEH
- //A reference counting handle keeps track of the number of existing copies
- //of the handle. The object managed by the handle is not destroyed until
- //the destructor of the last handle object is called.
- //Converting the handle to a template:
- template<class T>
- class Handle
- {
- private:
- T* imp;
- int* RefCount;
- public:
- Handle() : imp(0), RefCount(new int(0)) { }
- Handle(T* in) : imp(in), RefCount(new int(1)) { }
- //Destructor deletes managed object when reference count is zero
- ~Handle();
- //Copy constructor increments the reference count
- Handle(const Handle<T>& rhs);
- //Assignment operator decrements lhs reference count and
- //increments rhs reference count
- Handle<T> operator=(const Handle<T>& rhs);
- //Support using the handle object as a pointer
- T* operator->() {return imp;}
- T& operator*() {return *imp;}
- };
- //Destructor deletes managed object when reference count is zero
- template<class T>
- Handle<T>::~Handle()
- {
- //RefCount can be zero if this handle was never assigned an object.
- //In this case subtracting can cause a negative value
- if (--(*RefCount) <= 0) //not thread-safe {
- delete imp;
- delete RefCount;
- imp = 0;
- RefCount = 0;
- }
- }
- //Copy constructor increments the reference count
- template<class T>
- Handle<T>::Handle(const Handle<T>& rhs)
- : imp(rhs.imp), RefCount(rhs.RefCount) //not thread-safe
- {
- ++(*RefCount);
- }
- //Assignment operator decrements lhs reference count and
- //increments rhs reference count
- template<class T>
- Handle<T> Handle<T>::operator=(const Handle<T>& rhs)
- {
- if (this == &rhs) return *this; //no assignment to self
- //Delete our current implementation (LVAL)
- Handle::~Handle();
- //This is our new implementation (RVAL):
- imp = rhs.imp;
- RefCount = rhs.RefCount; //not thread-safe
- ++(*RefCount);
- return *this;
- }
- #endif //end of #ifndef HANDLEREFCOUNTTEMPLATEH
This example shows the reference counted handle in use with MyClass from the first example in this article.
Expand|Select|Wrap|Line Numbers
- void Process(Handle<MyClass> in)
- {
- //Silly stuff to exercise the handle
- Handle<MyClass> x;
- x = in;
- Handle<MyClass> y(x);
- y->seta(30); //Use handle object as a pointer
- }
- int main()
- {
- //Create the MyClass object and the handle
- //
- Handle<MyClass> hobj = CreateMyClassHandle(10,20);
- cout << *hobj << endl;
- Process(hobj);
- cout << *hobj << endl;
- //
- //Unused Handle
- //
- Handle<MyClass> unused;
- ////////////////////////////////////////////////////////////////////////
- return 0;
- }
You create a handle by using a create function. Let's assume you have a Person class:
Expand|Select|Wrap|Line Numbers
- class Person
- {
- private:
- string name;
- string address;
- public:
- Person(string name, string address);
- etc...
- };
Expand|Select|Wrap|Line Numbers
- Handle<Person> hp = CreatePersonHandle("John Smith", "123 Fox St Anytown USA");
Expand|Select|Wrap|Line Numbers
- Handle<Person> CreatePersonHandle(string name, string address)
- {
- Person* temp = new Person(name, address);
- Handle<Person> rval(temp);
- return rval;
- }
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
- int main()
- {
- Handle<Person? h1 = CreatePersonHandle("John Smith", "123 Fox St Anytown USA");
- Handle<Person? h2 = CreatePersonHandle("Sue Collins", "James Boulevard ACity USA");
- vector<Handle<Person> > database;
- database.push_back(h1);
- database.push_back(h2);
- cout << *database[0] << endl;
- cout << *database[1] << endl;
- }
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
- bool operator<(Handle<Person> left, Handle<Person> right)
- {
- if (*left < *right) return true; //calls Person::operator<
- return false;
- }
Expand|Select|Wrap|Line Numbers
- bool operator<(Handle<Person> left, Handle<Person> right)
- {
- if (left->GetName() < right->GetName()) return true;
- return false;
- }
Copyright 2007 Buchmiller Technical Associates North Bend WA USA
Revision History:
Feb 6, 2008: Corrected reference count error in Handle destructor.