tags:

views:

1742

answers:

7

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
{
    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
{
    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?

+1  A: 

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
Actually, this triggered what I meant -- I could directly call new with the copy constructor when I needed that particular class.
JohnMcG
A: 

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
+4  A: 

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
+7  A: 

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
Like this. To make it look like things from the standard propose a name change to make_clone() (Like make_pair<>).
Martin York
Bonus tip: make it a friend, and make member clone() private.
MSalters
+1  A: 

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
+1  A: 

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
What is the !! for?
michalmocny
I thought it was required to force the implicit conversion to bool: Instead it's redundant. Thanks
Nicola Bonelli
+5  A: 

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