JohnMcG
JohnMcG

Reputation: 8815

What's the best signature for clone() in C++?

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?

Upvotes: 26

Views: 13121

Answers (8)

Daniel
Daniel

Reputation: 8441

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);
    }
}

Upvotes: 1

MSalters
MSalters

Reputation: 180001

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); }
};

Upvotes: 29

Nicola Bonelli
Nicola Bonelli

Reputation: 8287

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.

Upvotes: 2

ididak
ididak

Reputation: 5868

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.

Upvotes: 1

Matt Cruikshank
Matt Cruikshank

Reputation: 2968

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();
}

Upvotes: 19

Gorpik
Gorpik

Reputation: 11038

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.

Upvotes: 7

Rob Walker
Rob Walker

Reputation: 47482

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.

Upvotes: 0

coppro
coppro

Reputation: 14516

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.

Upvotes: 5

Related Questions