Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / C++
Article

A ref-counted pointer class that supports polymorphic types

Rate me:
Please Sign up or sign in to vote.
1.78/5 (6 votes)
12 Nov 2002CPOL5 min read 117.9K   625   21   34
A ref-counted pointer class that supports polymorphic types

Objective

Memory management of objects, that are used from multiple threads, often lead to access violations, because one thread MAY delete the object while it is still used by another thread. This led to the thought of defining the lifetime of an object by the existence of atleast one reference to the object.

Design Requirements

  • Usage syntax: Since the pointer class is to be retro-fitted into existing code, the changes required in the code should be minimal. So the usage of the smart pointer should resemble the syntax for using a raw pointer as closely as possible.
  • Polymorphism support: Most systems use abstract classes to define interfaces that are inherited by multiple classes. This is done to seperate the interface from the implementation. In such systems, there are bound to be containers of interface pointers.

In a polymorphic object, the addresses of different interface pointers are different. Consider a class MyClass inheriting from IinterfaceA and IinterfaceB.

  • Problem 1: Smart pointers pointing to the same object can be of different types.
  • Problem 2: Some of the types may be abstract and delete cannot be called on those objects without causing slicing.
  • Problem 3: Pointers to the same object will have different values.
MyClass* pClass = new MyClass;
IInterfaceA* pIntA = pClass;
IInterfaceB* pIntB = pClass;
In this example, pClass, pIntA and pIntB point to the same object, but their values are different. Why? because each interface is at a different offset in memory.

The solution

Consider problem 3 first. How can the start address of the object be obtained? C++ standard says that dynamic_cast<void*> always points to the start of the object. If this conversion is applied to the address, before updating the address based reference count or do comparison operations, problem 3 is solved.

But, is it? No, because dynamic_cast works only on polymorphic types. For non-polymorphic types, the code will not compile. So, dynamic_cast should be performed only if the object is polymorphic. Run Time Type Information (RTTI) can be used to do this. There is a keyword typeid, that evaluates an expression at runtime, only if it is polymorphic. Using a comma expression as follows:

// Template function because the type that comes in is 
// not known at compile time
template <typename U>
bool is_polymorphic(U* p)
{
   bool result = false;
   typeid(result=true, *p);
   return result;
}
How does this work? Look at the expression (result=true, *p). This expression always returns *p, the rightmost value. The keyword typeid evaluates an expression only if the type is polymorphic. So result becomes true only when p points to an object of a polymorphic class. The start address of the object can also be obtained similarly.
// Template function because the type that comes in is not 
// known at compile time
template <typename U>
void* get_pm_ptr(U* p)
{
   void* x = static_cast<void*>(p);
   typeid(x = dynamic_cast<void*>(p), *p);
   return x;
}
As you can infer, the dynamic_cast is done only if the object is of a polymorphic class.
But, there is one problem. The operator typeid throws an exception if p is NULL. Hence,
// Template function because the type that comes in is not 
// known at compile time
template <typename U>
void* get_pm_ptr(U* p)
{
   void* x = static_cast<void*>(p);
   if (x != NULL)
     typeid(x = dynamic_cast<void*>(p), *p);
   return x;
}

Finally, to solve problem 1 and 3, reference count should be maintainted not by type, but by address. This can be done only on a global basis. We create a static object, that maintains a map of addresses to their reference count. The pointer class uses this static object to update the reference count and to find whether the object should be deleted because the ref count is zero.

Problem 2: The Deletion Problem: If the smart pointer is of an incomplete type or if it points to one of the inherited classes, then deletion cannot be done, if the classes do not implement a virtual destructor. So an IDelete interface class is provided. It has a single pure virtual function. We manadate that all polymorphic classes, that need to be used with this smart pointer, implement this interface - if it does not have a virtual destructor. The pure virtual function is named self_delete(). The implementation of self_delete in your polymorphic class should be as follows:
public:
virtual void self_delete()
{
   delete this;
}
The smart pointer will now call self_delete on the pointer after casting it to type IDelete, if the object implements IDelete; otherwise a raw delete is performed.

Warning: If you do not implement IDelete on a polymorphic class with no virtual destructors, the behaviour is undefined.

Usage Syntax

By overloading the =, ->, ==, !, !=; implementing a consructors that take raw pointers and implementing smart pointer copy constructor, the raw pointer usage syntax is maintained. The only compromise is the C++ casting operators - static_cast, dynamic_cast etc.
// myclass inherits from iinterface1 and iinterface2
_mm::_ptr<myclass> x = new myclass; 

// the following line will not compile
_mm::_ptr<iinterface1> y = dynamic_cast<IINTERFACE1*>(x); 
// the correct usage is
_mm::_ptr<iinterface1> y = dynamic_cast<IINTERFACE1*>(x.ptr()); 

Dependencies

The code should be compiled with the 'Enable RTTI' option in the "Project | Settings | C/C++ tab | C++ language".
It uses STL map class.

Using in your code

Include smart_ptr.h in your project. The class is called _mm::_ptr and is used as
// myclass inherits from iinterface1 and iinterface2
_mm::_ptr<myclass> x = new myclass; 
// ref count for object is now 1
_mm::_ptr<iinterface1> y = dynamic_cast<iinterface1* >(x.ptr()); 
// ref count for object is now 2
_mm::_ptr<iinterface2> z = dynamic_cast<iinterface1* >(x.ptr()); 
// ref count for object is now 3

z = dynamic_cast<iinterface1* >(x.ptr();                
// ref count is still 3. this takes care of circular references.
z = NULL;                           // ref count is 2
y = NULL;                           // ref count is 1
x = NULL;                           // ref count is 0 - object deleted

Testing

You can use the test project to do some performance tests. I got approximately 47 nano seconds for an assign operation in my test on a PII/266. Please give me suggestions to improve this further. Particluarly, any attempts at code optimization is welcome<CODE>.

Bug fixes/Updates

  1. Added an IDelete interface to prevent slicing when dealing objects with multiple inherited classes.
  2. Changed the deletion to delete the object directly, if an implementation of IDelete does not exist. This was required because RTTI interpreted polymorpism by existence on one virtual function and most classes have one virtual destructor.
  3. Added a function ptr() to return the raw pointer, so that dynamic_cast can be performed without assignment to a raw pointer.
  4. Added critical sections to make the global map access thread safe.

Credits

As pointed out by William Kempf, the IDelete interface is not required if your classes have virtual destructor. If not, the problem is not slicing, the result is undefined. I have retained the IDelete in case someone chooses to use this class with a polymorphic class with no virtual destructor.

Greg Vogel pointed out the missing copy assignment operator.

He also pointed out the pointer cannot be passed accross module boundaries, because each module works using a different copy of the static map. Greg has suggested use of a memory mapped file to resolve this issue, which he has already successfully implemented.

Giving more thought to this problem, it seems that the ideal way to pass pointers to other modules or libraries is to pass them as raw pointers. The main application SHOULD maintain a reference counted pointer, until the module needs this raw pointer. It is recommended that modules do not store the raw pointers from smart pointers internally and get them from the main app with every function call. This was the initial reason why rawpointer support was introduced. Any changes to this premise have to be implemented by the user of this class.

Chen Sheng tested the code extensively and provided valuable contributions to fix bugs in the code.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralProblem with assignment Pin
Greg_AKA_MonkeyCoder2-Nov-02 23:22
sussGreg_AKA_MonkeyCoder2-Nov-02 23:22 
GeneralRe: Problem with assignment Pin
User 98853-Nov-02 5:54
User 98853-Nov-02 5:54 
GeneralIssues With Map Management Pin
1-Nov-02 22:21
suss1-Nov-02 22:21 
GeneralRe: Issues With Map Management Pin
Greg_AKA_MonkeyCoder2-Nov-02 0:14
sussGreg_AKA_MonkeyCoder2-Nov-02 0:14 
GeneralRe: Issues With Map Management Pin
User 98852-Nov-02 6:17
User 98852-Nov-02 6:17 
GeneralRe: Issues With Map Management Pin
User 98852-Nov-02 6:18
User 98852-Nov-02 6:18 
GeneralRe: Issues With Map Management Pin
Greg_AKA_MonkeyCoder2-Nov-02 23:23
sussGreg_AKA_MonkeyCoder2-Nov-02 23:23 
GeneralRe: Issues With Map Management Pin
User 98853-Nov-02 5:53
User 98853-Nov-02 5:53 
GeneralRe: Issues With Map Management Pin
User 98852-Nov-02 6:22
User 98852-Nov-02 6:22 
GeneralRe: Issues With Map Management Pin
Greg_AKA_MonkeyCoder2-Nov-02 23:24
sussGreg_AKA_MonkeyCoder2-Nov-02 23:24 
GeneralIdea for next release..... Pin
whunsley6-Oct-02 13:44
whunsley6-Oct-02 13:44 
GeneralRe: Idea for next release..... Pin
User 98856-Oct-02 14:46
User 98856-Oct-02 14:46 
GeneralRe: Idea for next release..... Pin
Brett Delle Grazie6-Oct-02 19:37
Brett Delle Grazie6-Oct-02 19:37 
GeneralRe: Idea for next release..... Pin
User 98857-Oct-02 2:17
User 98857-Oct-02 2:17 
GeneralIt's not work correctly. Pin
meteor6-Oct-02 2:14
meteor6-Oct-02 2:14 
GeneralRe: It's not work correctly. Pin
User 98856-Oct-02 5:11
User 98856-Oct-02 5:11 
GeneralRe: It's not work correctly. Pin
meteor6-Oct-02 7:06
meteor6-Oct-02 7:06 
GeneralRe: It's not work correctly. Pin
User 98857-Nov-02 6:22
User 98857-Nov-02 6:22 
GeneralRe: It's not work correctly. Pin
User 98856-Oct-02 5:15
User 98856-Oct-02 5:15 
GeneralRe: It's not work correctly. Pin
meteor6-Oct-02 6:38
meteor6-Oct-02 6:38 
GeneralRe: It's not work correctly. Pin
User 98856-Oct-02 6:56
User 98856-Oct-02 6:56 
Generalperblem! Pin
meteor5-Oct-02 2:51
meteor5-Oct-02 2:51 
GeneralRe: perblem! Pin
User 98855-Oct-02 5:28
User 98855-Oct-02 5:28 
GeneralRe: perblem! Pin
User 98855-Oct-02 5:44
User 98855-Oct-02 5:44 
GeneralRe: perblem! Pin
meteor5-Oct-02 6:57
meteor5-Oct-02 6:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.