Clone Smart Pointer (clone_ptr)

Introduction

The clone_ptr class is a smart pointer that can clone itself without requiring the base class to have a clone method. It can clone itself to the type pass to the constructor. The constructor for the clone_ptr is a template method, which is able to store type information for later usage when cloning the pointer.

The clone_ptr helps solve two main problems when using abstract pointers. It helps solve problems associated with having STL containers of abstract pointers, and it helps solve problems associated with doing a deep copy of an abstract pointer.

The clone_ptr class is a good choice for use with STL containers, because the clone pointer has operator methods for assignment (equals) operator, equality operator, and greater then operator that work like concrete object operators. That means when a sort is performed on a container of clone pointers the object being pointed to is sorted, and not the pointer address. It also means that a container of abstract pointers can be copied from one container to another, or from one part of a container to another, without resulting in having the copied object pointing to the same data.

Unlike share pointers, the clone_ptr class does not share its pointer, and it’s based on the idea of strict pointer ownership logic. This logic includes owning the pointer to the object from the time of its creation until its destruction.

The clone_ptr allows for automatic deep copy of abstract pointers. Without the clone_ptr, it’s difficult to create a copy constructor for an object that contains an abstract pointer.

class foo
{
   AbstractClass * m_MyAbstractClassPtr;
public:
   foo(AbstractClass * abstractclassptr_)
      :m_MyAbstractClassPtr(abstractclassptr_){}

   foo(const foo& Src)
   {
      //Without a clone_ptr, there's no C++ mechanism to determine
      //how to copy the object pointed to by the abstract pointer
   }
};

The above copy constructor would be difficult to implement without the use of the clone_ptr. The common workaround solution for the above code is to add a virtual clone method to the abstract class, and then call the clone method to create a copy. With this method, every descendant class would have to create implementation for the clone method.

By using a clone_ptr class, the correct derived object is copied, without requiring the base class to have a clone method. The copy is performed through the assignment (equals) operator.

class foo
{
   clone_ptr < AbstractClass > m_MyAbstractClassPtr;
public:
   foo(const clone_ptr < AbstractClass >
       &abstractclassptr_):m_MyAbstractClassPtr(abstractclassptr_){}
      //Or
   tempalte < class T >
   foo(T* derivedtype):m_MyAbstractClassPtr(derivedtype){}
   foo(const foo& Src)
   {
      //In the following line of code, the clone pointer will copy
      //the correct derived class without the need of a clone method
      m_MyAbstractClassPtr = Src.m_MyAbstractClassPtr;
   }
};

Background

I decided to create a new smart pointer, because the common available smart pointers are not well suited for STL containers. According to IAW C++ standards, the std::auto_ptr can not be used at all with STL containers. The boost shared_ptr class does not keep ownership of its pointer, and instead shares it. That means a shared_ptr type of smart pointer would not be a good choice if you need to copy one container to another, or one part of a container to another.

One of the main logic differences between the clone_ptr class and other smart pointers like boost::shared_ptr and auto_ptr is that the clone_ptr attempts to emulate a concrete type via pointer interface, where as boost::shared_ptr and std::auto_ptr attempts to emulate a pointer. The disadvantage of emulating a pointer is that it brings in logic that is not usually beneficial, and often detrimental to the desired effect.

An example of undesirable pointer behavior is the following comparison operator:

void CompareFoo(foo* foofoo1, foo* foofoo2)
{
   if (foofo1 == foofoo2)    //Does NOT compare the foo object, and
                             //instead only compares pointer address
   {
   ............


void CompareFoo(shared_ptr < foo > foofoo1,
                shared_ptr < foo > foofoo2)
{
   if (foofo1 == foofoo2)    //Does NOT compare the foo object,
                             //and instead only compares pointer
                             //addresses
   {
   ............

Normally, in the above code, the desired logic is to test whether what one pointer is pointing to is equal in value to what the other pointer is pointing to. However, because the above code is not de-referencing the pointers, it actually compares only the addresses of the pointers.

With a clone_ptr, the comparison is made on the object being pointed to and not on the address of the pointer.

void CompareFoo(clone_ptr < foo > foofoo1,
                clone_ptr < foo > foofoo2)
{
   if (foofo1 == foofoo2)    //Does compare the foo object
   {

In a container of a smart pointer, it’s rarely required no desired to make a comparison on the pointer address, and instead, it’s more beneficial and practical to have the operators apply logic to the deference pointer instead of the pointer itself. That’s the main reason why the clone_ptr class does not try to do a 100% pointer emulation; instead, it’s more of a hybrid in which it emulates pointers where it’s beneficial, but emulates concrete types when applying most comparison operators.

Another STL container problem that the clone_ptr solves is problems associated with having a container of a type that has a constant data member.

Whenever I develop a class, I like to try to make as many of the data members contant as possible. However, when I try to use my class with a vector, I then have to remove the constantness of the data members.

Example:

class ObjectWithConstantMembers
{
public:
   ObjectWithConstantMembers(const std::string &Name,
                             int Age):m_Name(Name), m_Age(Age){}
   const std::string m_Name;
   const int m_Age;
};

void DemoUsingObjWithConstantDataMembers()
{
   std::vector<ObjectWithConstantMembers> vecObj1;
   vecObj1.push_back(ObjectWithConstantMembers("David", 44));
   ............

The above code will not compile because of the constant data members. If the above code used clone_ptr, it then would be able to compile and function.

Example:

void DemoUsingObjWithConstantDataMembers()
{
   std::vector< clone_ptr < ObjectWithConstantMembers > > vecObj2;
   vecObj2.push_back(new ObjectWithConstantMembers("David", 44));
   vecObj2[0] = new ObjectWithConstantMembers("Axter", 33);
   ............

The above code works with clone_ptr because the clone_ptr assignment (equals) operator doesn’t call the object’s equals operator; instead, it deletes the current pointer and creates a new object via the object’s copy constructor.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read