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:
- #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.
- #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.
- #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.
- 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:
- class Person
- {
- private:
- string name;
- string address;
- public:
- Person(string name, string address);
- etc...
- };
- Handle<Person> hp = CreatePersonHandle("John Smith", "123 Fox St Anytown USA");
- 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> >:
- 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:
- bool operator<(Handle<Person> left, Handle<Person> right)
- {
- if (*left < *right) return true; //calls Person::operator<
- return false;
- }
- 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.
Share this article on:
Twitter Facebook StumbleUpon DiggLast edited by weaknessforcats; Feb 6 '08 at 02:38 PM.Reason: Fixed bug: Changed handle destructor to: if (--(*RefCount) <= 0)//not thread-safe
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
- 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;
- }
a = b;
Do we delete a or b ?
Sorry for my stupidity.
Thanks for your help.
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.
- Handle Classes: The Smart Pointer
- Handle Classes: The Smart Pointer
- Smart Pointer
- Smart pointer
- smart pointer
- Smart Pointer
- Smart Pointer
- Smart pointer
- smart pointer
- smart pointer
- smart pointer
- smart pointer
- smart pointer / shared pointer / normal pointer
- C++ handle classes
- C++ handle classes
- Smart Pointer访谈录
- Smart Pointer--智能指针
- Smart Pointer访谈录
- 多进程服务器中,epoll的创建应该在创建子进程之后
- MSComm 控件
- string、wstring、cstring、 char、 tchar、int、dword转换方法
- MSComm 控件
- MSComm 控件
- Handle Classes: The Smart Pointer
- JVM 调优总结
- Android之PreferenceActivity
- bootload启动流程(二)----Eboot的主要流程
- 进一步学习PV操作——统一于生产者消费者问题
- Bullet的3D Max插件
- 解决Boost.Regex对中文支持不好的问题
- Windows、VMware、Linux及开发板间的网络连接
- 能源之战---盗梦空间的真实含义