Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A ref-counted pointer class that supports polymorphic types

0.00/5 (No votes)
12 Nov 2002 1  
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(x); 
// the correct usage is

_mm::_ptr<iinterface1> y = dynamic_cast(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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here