Question

If I have an abstract class Drink which has a derived class Coffee, what would the copy constructor for Coffee look like? If it accepts a reference of Type Coffee as a parameter then I wouldn't be able to pass an instance which has been instantiated like this: Drink* coffee = new Coffee(..); But if I accept references to Drink then I wouldn't have means of knowing if the Drink is indeed of type Coffee or Tea for example.

Was it helpful?

Solution

The copy constructor – by very definition – always takes a reference to the exact same type it was declared for. So the copy constructor signature for Coffee would be Coffee(const Coffee&). Any other constructor is not a copy constructor.

The copy constructor may be used as a normal ctor, but is used implicitly by the language for copy assignment Coffe c = other_coffee, which is then equivalent to Coffee c(other_coffee), and when passing the object by value to a function, e.g. drink(Coffee) requires a copy.

When you have a Drink that may be a Coffee or some other Drink subclass, this necessarily means you have a pointer or reference to a Drink. In such a case, it is usually not necessary to create a copy of the pointed-to value: you can simply copy the pointer.

If you do in fact need to copy a Drink, you will have to create a virtual method so that every kind of Drink can clone itself:

class Drink {
public:
  virtual ~Drink() = default;
  virtual std::unique_ptr<Drink> clone() const = 0;
  ...
};

class Coffee : public Drink {
  ...
  Coffee(Coffee const&);
  std::unique_ptr<Drink> clone() const override;
};

std::unique_ptr<Drink> Coffee::clone() const {
  return std::make_unique<Coffee>(*this);  // requires C++ 14
}

Then: std::unique_ptr<Drink const> a = ...; std::unique_ptr<Drink const> b = a->clone(). Of course you can also use raw pointers, if you're into that kind of thing.

OTHER TIPS

But if I accept references to Drink then I wouldn't have means of knowing if the Drink is indeed of type Coffee or Tea for example

You cannot copy construct a subclass (Coffee) from any and all arbitrary objects that sub-classes from super class Drink. So, the answer has to be that you must limit the copy constructor accept the same class (Coffee) for the copy constructor.

You are correct in noting that you will not be able to invoke the desired copy constructor from Drink *coffee = new Coffee(...);. However, if you do Coffee coffee(...);, you will be able to use the copy constructor.

One of the main uses of the copy constructor is that the compiler automatically calls it when passing objects (object values, not pointers or references) to functions and returning them. As such, the copy constructor is for copying values, and those values that are presumed to have a specific compile-time known type.

So, you are considering using copy by value with objects by pointers and subclassing/polymorphism, which is pretty weird territory. The copy by value mechanism assumes you know, at compile-time, the desired type (and size to allocate) of the resulting entity copy.

I suspect that instead you want to clone the dynamic or runtime type of the object, not the static or compile-time type of the object. To clone the dynamic type of the object you'll need to introduce a virtual clone method. However, you can invoke the virtual clone method from your copy constructor, which may provide you what you want.

You can search for the "virtual copy constructor" pattern.

Conversion functions are the answer to this

As the copy ctor always accepts it's own type as the argument, there's no scope of accepting a Drink in the copy-ctor of Coffee.

class Coffee: public Drink
{
    ...
public:
    Coffee(const Coffee& other);
    ...
};

If you still want to initialise a Coffee from a Drink(possibly Coffee), you can use a conversion operator. Note that you want to decide what to do if the Drink is NOT a Coffee. I have in below code thrown an exception so that if the source data is not a Drink, I don't want to proceed with creating a Coffee object.

class Coffee: public Drink
{
    ...
public:
    //Standard copy-ctor
    Coffee(const Coffee& other);

    //Conversion function
    Coffee(const Drink& drink)
    {
        const Coffee& other = dynamic_cast<const Coffee&>(drink);
        //this->x = other.x;
        //this->y = other.y;
    }
};

This way, if the passed reference is of a different type (say Drink or Tea), a std::bad_cast exception is thrown and the creation of object is aborted.

. . .

//d1: drink reference pointing to object of type 'Drink' OR 'Tea' OR 'Coffee'
try
{
    Coffee c1(d1);
    cout << "Created coffee from drink" << endl;
}
catch(bad_cast& ex)
{
    cout << "Exception : " << ex.what() << endl;
}

Above code will work fine for Coffee object and throw exceptions for any other type.

Should you desire to work it differently, you can handle the exception in the ctor and go from there. Note that handling exceptions in the ctor is generally frowned upon, with good reason.

NOTE THAT dynamic_cast will work for polymorphic hierarchies i.e. with at least 1 virtual function in the base class.

. . .

Licensed under: CC-BY-SA with attribution
scroll top