Smart pointers in C++ are nice, yet fraught with irregularities. One is the inability to create a proper clone function. This requires a feature called covariant return types, which C++ supports, but only for pointers. That sounds okay, but despite their name, smart pointers are not actually pointers.

Covariant return

A prototypical example of covariant return types is a clone method. Reasonably, we expect that a ‘clone’ method returns the type on which it was called. It wouldn’t make sense to call it ‘clone’ if it could return an incompatible object type.

B * b = create_b(); //with covariant returns B * dup = b->clone(); //without covariant returns B * dup = static_cast<B*>(b->clone());

The above covariant call can be implemented as below. Here we see a derived class overriding the virtual function with a different return type. This is well defined so long as the return is a derived type.

class A { virtual A * clone(); } class B : public A { virtual B * clone(); } class C : public B { virtual C * clone(); }

The caller of clone is returned the same type without having to cast it. The virtual function still works as expected: if you have a ‘B*’ to an object of type ‘C’, the ‘C::clone’ method will be called and its return cast to a ‘B*’. Beyond just a convenience, it is safer to have the compiler do this than rely on a ‘static_cast’. As code changes over time, one of those ‘static_cast’ statements will inevitably perform an invalid cast.

Broken with shared_ptr

I consider ‘shared_ptr’ to be a good option for general purpose object management. Unfortunately it isn’t a real pointer. Only via a series of implicit conversion functions does it behave somewhat like a pointer. Weaknesses show up in many situations: covariant returns are one of them. It is not possible to code a ‘clone’ function as expected.

struct A { virtual shared_ptr<A> clone() { return make_shared<A>(*this); } }; struct B : public A { shared_ptr<B> clone() { return make_shared<B>(*this); } };

Covariant returns are restricted to true pointer types. The above code results in a compiler error. Using a ‘smart_ptr’ requires returning the same type in all overrides of the virtual function. Using these non-covariant clone methods requires an unsafe cast.

shared_ptr<B> b = create_b(); //requires this unsafe syntax shared_ptr<B> ugh = static_pointer_cast<B>( b->clone() ); //the preferred syntax shared_ptr<B> good = b->clone();

Using macros and/or other template techniques the overhead can be reduced somewhat. But it’s code I shouldn’t have to write. Indeed, a minor error in one of my versions resulted in a hard-to-find defect. It’s a defect that happened because a new C++ feature, smart pointers, are effectively incompatible with a previous feature, covariant returns. I’m in favour of new language features, but they should expand on previous features, not deprecate them. As it stands, a proper clone function using smart pointers cannot be written.