04 May 2004

Cloning and smart pointers

Let's suppose we have a hierarchy of classes that provides a "virtual copy constructor":

class A
{
public:
 // This function returns a pointer to a copy of this object
 virtual A* Clone() const;
};

class B : public A
{
public:
 A* Clone() const;
};

// class C : public A
// ...

We would like to let a smart pointer do the memory handling. Obtaining a copy of an object can be done like this:

MySmartPtr<A> a(new A());
MySmartPtr<A> b(a->Clone());

All is nice and clean. But what do you do if you want to call a function f and you want to send it a copy of a and you do not need (locally) a name for this copy?

// void f(MySmartPtr<A> x);
MySmartPtr<A> a(new A());
f(MySmartPtr<A>(a->Clone));

I have written MySmartPtr<A> because the constructor of a smart pointer is usually explicit: you don't want to acquire ownership without explicitly saying so. However the cloning method is a special case. Here you do want to obtain a smart pointer when you say "Clone". What to do?

One solution is to change the Clone methods to return MySmartPtr<A>. This is too intrusive. Classes that have a functionality related to, say, representing accounts or language constructs should not worry about memory handling. Better is to let the smart pointer act as a proxy for the Clone method.

template <typename T>
class MySmartPtr
{
 // ...
public:
 MySmartPtr<T> Clone() const
 {
  if (obj == NULL) return *this;
  else return MySmartPtr<T>(obj->Clone());
 }
};

The result is that you write:

MySmartPtr<A> a;
f(a.Clone());

This can be generalized by using a policy to specify the method by which the clone is obtained. In the code above it is always done by calling a method named "Clone". Again, this is unrealistic: we should not change classes to "fit" to our smart pointer. But I will not discuss this further.

Instead I want to discuss an alternative to the smart pointer member function, namely a friend function. If you have implemented the smart pointer methods in a separate file and you use explicit instantiation as a workaround for the lack of support for export then this solution might be of interest. When Clone is a member function then it is always instantiated and you cannot have smart pointers to classes that do not implement Clone (again, if you do not use policies with static assertions).

The friend function alternative looks like this:

template <typename T> class MySmartPtr;

template <typename T>
MySmartPtr<T> Clone(const MySmartPtr<T>& ptr)
{
 // do the same as in member function
}

template <typename T>
class MySmartPtr
{
public:
 friend MySmartPtr<T> Clone<T>(const MySmartPtr<T>& ptr);
 // ...
}

Now you type:

MySmartPtr<A> a;
f(Clone(a));

I've been very sketchy in this entry but I don't have much time :(