Tree Nguyen
Tree Nguyen

Reputation: 1199

How to implement clone in a pure abstract class?

So I want to override the pure abstract method in my derived classes but I got this error. Can someone help me see what happened and how can I complete it.

My Device class;

class Device  {
public:
    Device();
    Device(const Device& orig);
    virtual ~Device();

    virtual Device Clone() = 0;
}

And my derived class;

class Radar : public Device {
public:
    Radar();
//    Radar(const Radar& orig); // commenting so the compiler using its default copy constructor
    virtual ~Radar();

    Radar Clone();    
};

Source file for my Radar class;

Radar Radar::Clone() {
    return *(new Radar(*this));
}

If I use Device type in my Clone method in Device class, it will pop-up that Device is an abstract class.

If I use void type (which I'm assuming it's not what I want to have), it will show that I haven't implement this method.

What should I do?

Upvotes: 0

Views: 1171

Answers (3)

Niall
Niall

Reputation: 30605

A classic approach to implementing this cloning technique is to use covariant return types for the Clone() method. Coupled with modern RAII techniques (e.g. unique_ptr et. al.) it offers a very flexible and safe combination to manage and clone the objects appropriately.

One of the advantages of the covariant return type is that you are able to obtain a clone an object (a deep copy) and the return type is at the same level in the hierarchy as the argument (i.e. the return is not always to the base class) and no immediate casting is required. In C++, pointers and references support covariance, values do not support covariance.

Using a smart pointer such as unique_ptr is advised over raw painters to avoid memory leaks. The clone_unique factory is modelled on the corresponding make_unique utility from the standard library and returns a unique_ptr. It contains explicit type checks on the class hierarchy of the argument and target types.

The solution does require use of std::unique_ptr. If not available with your compiler, boost provides alternatives for these. There are a few other newer C++ language features, but these can be removed if required.

#include <type_traits>
#include <utility>
#include <memory>

class Device {
public:
  virtual Device* Clone() const = 0;
};

class Radar : public Device {
public:
  virtual Radar* Clone() const override {
    //    ^^^^^^ covariant return compared to Device::Clone
    return new Radar(*this);
  }
};

// Clone factory

template <typename Class, typename T>
std::unique_ptr<Class> clone_unique(T&& source)
{
  static_assert(std::is_base_of<Class, typename std::decay<decltype(*source)>::type>::value,
    "can only clone for pointers to the target type (or base thereof)");
  return std::unique_ptr<Class>(source->Clone());
}

int main()
{
  std::unique_ptr<Radar> radar(new Radar());
  std::unique_ptr<Device> cloned = clone_unique<Device>(radar);
}

Sample code.

See this related answer for a longer example.

Upvotes: 1

Tony Delroy
Tony Delroy

Reputation: 106106

Your Clone method will need to return pointers to the cloned objects... covariant return types only work that way (as returning by value is asking the caller to copy the returned value to the stack - that would be a memory leak when you've allocated it with new).

So, it should be:

virtual Device* Clone() = 0;

...and later...

Radar* Clone();    // YES, it should be Radar* here - that uses C++'s support for
                   // "covariant return types", see also "UPDATE" discussion

Radar* Radar::Clone()
{
    return new Radar(*this);
}

UPDATE - further explanation as requested

So, the idea with a clone function is that it can return a deep copy of whatever actual derived type your Device* is currently addressing. Given that derived type might add data members that Device lacked, it could be a larger object, and the caller has no ability to reserve the right amount of stack space in which to store it. For that reason, the object needs to be allocated dynamically with new, and the predictably-sized Device* is the caller's way to access the new object. It's legal for the clone function to return a Radar* though - all that means is that client code that knows at compile time that it is dealing with a Radar and clones it can continue to use it as a Radar - accessing any extra members that Radar provides.

Hope that helps clarify things. You might also want to do some background reading on Object Oriented programming.

Upvotes: 3

user966379
user966379

Reputation: 2983

Please try the following signature

virtual Device& Clone() = 0; or virtual Device* Clone() = 0;

//Body

Device&  Radar::Clone() {
    return  Radar(*this));
}

Upvotes: -1

Related Questions