26

As Scott Myers wrote, you can take advantage of a relaxation in C++'s type-system to declare clone() to return a pointer to the actual type being declared:

class Base
{
    virtual Base* clone() const = 0;
};

class Derived : public Base
{
    virtual Derived* clone() const
};

The compiler detects that clone() returns an pointer to the type of the object, and allows Derived to override it to return a pointer to derived.

It would desirable to have clone() return a smart pointer that implies transfer of ownership semantics, like the following:

class Base
{
   virtual std::auto_ptr<Base> clone() const = 0;
};

class Derived : public Base
{
    virtual std::auto_ptr<Derived> clone() const;
};

Unfortunately, the relaxation of the conventions does not apply to templated smart pointers, and the compiler will not allow the override.

So, it seems I am left with two options:

  1. Have clone() return a "dumb" pointer, and document that clients are responsible for disposing of it.
  2. Have clone() return a smart base pointer, and have clients use dynamic_cast to save them to a Derived pointer if they need it.

Is one of these approaches preferred? Or is there a way for me to eat my transfer of ownership semantics and have my strong type safety too?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
JohnMcG
  • 8,709
  • 6
  • 42
  • 49

8 Answers8

29

Use the Public non-virtual / Private virtual pattern :

class Base {
    public:
    std::auto_ptr<Base> clone () { return doClone(); }
    private:
    virtual Base* doClone() { return new (*this); }
};
class Derived : public Base {
    public:
    std::auto_ptr<Derived> clone () { return doClone(); }
    private:
    virtual Derived* doClone() { return new (*this); }
};
MSalters
  • 173,980
  • 10
  • 155
  • 350
19

The syntax isn't quite as nice, but if you add this to your code above, doesn't it solve all your problems?

template <typename T>
std::auto_ptr<T> clone(T const* t)
{
    return t->clone();
}
Matt Cruikshank
  • 2,932
  • 21
  • 24
  • 4
    Like this. To make it look like things from the standard propose a name change to make_clone() (Like make_pair<>). – Martin York Nov 03 '08 at 23:23
  • 1
    Bonus tip: make it a friend, and make member clone() private. – MSalters Nov 04 '08 at 08:43
  • 1
    Nitpick: I'd have the function accept `T const & t` instead -- there isn't any need to use a pointer here. Other than that I really like this approach. – cdhowie Jan 28 '15 at 16:22
7

I think the function semantics are so clear in this case that there is little space for confusion. So I think you can use the covariant version (the one returning a dumb pointer to the real type) with an easy conscience, and your callers will know that they are getting a new object whose property is transferred to them.

Gorpik
  • 10,940
  • 4
  • 36
  • 56
  • +1, let the user pick up which memory management strategy is best for him... additionally it's also recommended for use with Boost Pointer Container :) – Matthieu M. Nov 08 '10 at 17:08
5

It depends on your use case. If you ever think you will need to call clone on a derived object whose dynamic type you know (remember, the whole point of clone is to allow copying without knowing the dynamic type), then you should probably return a dumb pointer and load that into a smart pointer in the calling code. If not, then you only need to return a smart_ptr and so you can feel free to return it in all overrides.

coppro
  • 14,338
  • 5
  • 58
  • 73
  • Actually, this triggered what I meant -- I could directly call new with the copy constructor when I needed that particular class. – JohnMcG Nov 05 '08 at 20:42
2

Tr1::shared_ptr<> can be casted like it were a raw pointer.

I think have clone() return a shared_ptr<Base> pointer is a pretty clean solution. You can cast the pointer to shared_ptr<Derived> by means of tr1::static_pointer_cast<Derived> or tr1::dynamic_pointer_cast<Derived> in case it is not possible to determine the kind of cloned object at compile time.

To ensure the kind of object is predictible you can use a polymorphic cast for shared_ptr like this one:

template <typename R, typename T>
inline std::tr1::shared_ptr<R> polymorphic_pointer_downcast(T &p)
{
    assert( std::tr1::dynamic_pointer_cast<R>(p) );
    return std::tr1::static_pointer_cast<R>(p);
}

The overhead added by the assert will be thrown away in the release version.

Nicola Bonelli
  • 8,101
  • 4
  • 26
  • 35
1

That's one reason to use boost::intrusive_ptr instead of shared_ptr or auto/unique_ptr. The raw pointer contains the reference count and can be used more seamlessly in situations like this.

ididak
  • 5,790
  • 1
  • 20
  • 21
1

Updating MSalters answer for C++14:

#include <memory>

class Base
{
public:
    std::unique_ptr<Base> clone() const
    {
        return do_clone();
    }
private:
    virtual std::unique_ptr<Base> do_clone() const
    {
        return std::make_unique<Base>(*this);
    }
};

class Derived : public Base
{
private:
    virtual std::unique_ptr<Base> do_clone() const override
    {
        return std::make_unique<Derived>(*this);
    }
}
Community
  • 1
  • 1
Daniel
  • 8,179
  • 6
  • 31
  • 56
  • This is missing the requested functionality that `derivedObj->clone()` can be used as a `Derived` without an extra `dynamic_cast`. I would keep the private `do_clone` functions returning raw pointers with covariant return type, and restore the public hiding `Derived::clone` with different return type. – aschepler Jun 02 '21 at 15:42
0

You could have two methods, a virtual clone() that returns a smart pointer wrapper around the base type, and a non-virtual clone2() that returns the correct type of smart pointer.

clone2 would obviously be implemented in terms of clone and encapsulate the cast.

That way can get the most derived smart pointer that you know at compile time. It may not be the most derived type overall, but it uses all the information available to the compiler.

Another option would be to create a template version of clone that accepts the type you are expecting, but that adds more burden on the caller.

Rob Walker
  • 46,588
  • 15
  • 99
  • 136