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.

Clone Smart Pointer (clone_ptr)

Using the Code

There are many benefits to using the clone_ptr over other smart pointers, but the following highlights some of the main benefits.

The clone_ptr allows for the copying of an object, instead of copying the pointer address.

void 
CopyCorrectDerivedTypeDemo() {
std::vector < clone_ptr < BaseClass> >  vBaseClass;
   //Set up data using base and derived type classes
   vBaseClass.push_back(new BaseClass( "1" ));
   vBaseClass.push_back(new Derived_B( "2" ));
   vBaseClass.push_back(new BaseClass( "3" ));
   vBaseClass.push_back(new Derived_A( "4" ));
   vBaseClass.push_back(new BaseClass( "5" ));

   //Copy contents from one container to another
   std::vector < clone_ptr < BaseClass > >
        vBaseClass_Copy(vBaseClass.begin(), vBaseClass.end());

   //Display results
   for (std::vector < clone_ptr < BaseClass >
        >::size_type i = 0;i < vBaseClass_Copy.size();++i)
   {
      vBaseClass_Copy[i]->WhoAmI();
   }

When sorting a container of clone_ptr, the object type is sorted instead of the pointer address.

void VectorSortDemo()
{
   std::vector < clone_ptr < BaseClass> >  vBaseClass;
   //Set up data using base and derived type classes
   vBaseClass.push_back(new BaseClass( "3" ));
   vBaseClass.push_back(new Derived_B( "2" ));
   vBaseClass.push_back(new BaseClass( "1" ));
   vBaseClass.push_back(new Derived_A( "5" ));
   vBaseClass.push_back(new BaseClass( "4" ));

   //Sort Data
   sort(vBaseClass.begin(), vBaseClass.end());

   //Display sorted data
   for (std::vector < clone_ptr < BaseClass >
        >::size_type i = 0;i < vBaseClass.size();++i)
   {
      vBaseClass[i]->WhoAmI();
   }

std::set and std::map container also are correctly sorted when using the clone_ptr.

void SetDemo()
{
   std::set < clone_ptr < BaseClass > >  sBaseClass;
   //Set up data using base and derived type classes
   sBaseClass.insert(new BaseClass( "3" ));
   sBaseClass.insert(new Derived_B( "2" ));
   sBaseClass.insert(new BaseClass( "1" ));
   sBaseClass.insert(new Derived_A( "5" ));
   sBaseClass.insert(new BaseClass( "4" ));

   //Display sorted data
   for(std::set < clone_ptr < BaseClass >
       >::iterator i = sBaseClass.begin();i!=sBaseClass.end();++i)
   {
      (*i)->WhoAmI();
   }

The equality operator assists in determining whether one container is equal to another.

void ContainerEqualityDemo()
{
   list < clone_ptr < BaseClass > >  PtrsX;
   PtrsX.push_back(new BaseClass("1"));
   PtrsX.push_back(new BaseClass("2"));
   PtrsX.push_back(new BaseClass("3"));

   list < clone_ptr < BaseClass > >  PtrsY;
   PtrsY.push_back(new Derived_B("4"));
   PtrsY.push_back(new Derived_B("5"));
   PtrsY.push_back(new Derived_B("6"));

   list < clone_ptr < BaseClass > > PtrsZ;
   PtrsZ.push_back(new Derived_A("1"));
   PtrsZ.push_back(new Derived_A("2"));
   PtrsZ.push_back(new Derived_A("3"));

   bool IsEqual1 = (PtrsX == PtrsY);    //Should be false
   bool IsEqual2 = (PtrsX == PtrsZ);    //Should be true

   cout << "Should be false (0) IsEqual1 = " << IsEqual1 << endl;
   cout << "Should be true  (1) IsEqual2 = " << IsEqual2 << endl;

The equality operator also helps with STL replace and remove functions.

void RemoveFromListDemo()
{
   std::list < clone_ptr < BaseClass> >  lBaseClass;
   lBaseClass.push_back(new BaseClass( "X" ));
   lBaseClass.push_back(new Derived_B( "2" ));
   lBaseClass.push_back(new BaseClass( "X" ));
   lBaseClass.push_back(new Derived_A( "5" ));
   lBaseClass.push_back(new BaseClass( "4" ));

   //Remove certain values
   lBaseClass.remove(clone_ptr < BaseClass >    //Remove all Xs
                     (new BaseClass("X")));

   //Display results
   for(std::list < clone_ptr < BaseClass >
       >::iterator i = lBaseClass.begin();i!=lBaseClass.end();++i)
   {
      (*i)->WhoAmI();
   }

Points of Interest

As previously stated, the clone_ptr uses strict pointer ownership logic. clone_ptr clones the type that is passed into the constructor.

class abstract
{
public:
   virtual void funcx()=0;
};
class derived : public abstract
{
   void funcx(){}
};

class derived_derived : public derived
{
   void funcx(){}
};
   abstract *a = new derived_derived;
   derived *d = new derived_derived;
   derived_derived *dd = new derived_derived;
   //clone_ptr<abstract> clptr1 = a;     //Will not compile
   clone_ptr<abstract>   clptr2 = d;     //Will compile, but will
                                         //clone wrong type
   clone_ptr<abstract>   clptr3 = dd;    //Gets correctly clone

In the above code, clptr1 will fail to compile because it cannot clone an abstract type. This happens because the constructor is incorrectly getting passed an abstract pointer. By making the base pointer an abstract type, it can help prevent the constructor from getting initialized to the wrong type. However, as shown in the above code, the wrong type can still be passed via a derived pointer, when using a derived derived type.

This is one of the main reasons why the clone_ptr class should not be used with assignment indirection; instead, it should have full control of the object from the time of its creation.

The clone pointer demo project includes another similar smart pointer called copy_ptr. The copy_ptr class is a more efficient version of the clone_ptr. However, copy_ptr will not compile on older compilers like VC++ 6.0. If portability to older compilers is not an issue, I recommend using copy_ptr over clone_ptr. I would like to thank Kai-Uwe Bux for the great idea of using a function pointer method in the copy_ptr class.

Conclusions

By using the clone_ptr, you get the advantages of a pointer without the disadvantages of pointer operators and potential memory leaks.

All the above code is in the demo project listed for download.



About the Author

David Maisonave

I'm a top 10 ranking member in both C++ and MFC topic areas at the Experts-Exchange.

http://www.experts-exchange.com/Cplusplus

http://www.experts-exchange.com/MFC

I started programming as a hobby in 1985 on my 8-bit Atari computer. I programmed in BASIC and in assembly language until I upgraded to a 16-bit IBM compatible in 1988. In 1989, I started programming in C using Turbo C++. IMHO, Turbo C/C++ was the best compiler available to the market for many years, until VC++ 4.0 came out.

I started using MFC with VC++ 4.0 in 1996, and I developed and sold an application called GameMenu95. It had limited success, and I retired the program in 1999.

My favorite language of choice is C++. I believe it has the best combination of flexibility, speed, and type safety.

Coding is still my hobby, and I'm very fortunate in that I get paid good money to engage in my hobby!

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • A majority of organizations are operating under the assumption that their network has already been compromised, or will be, according to a survey conducted by the SANS Institute. With many high profile breaches in 2013 occurring on endpoints, interest in improving endpoint security is top-of-mind for many information security professionals. The full results of the inaugural SANS Endpoint Security Survey are summarized in this white paper to help information security professionals track trends in endpoint …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds