21

The typical pattern when you want to copy a polymorphic class is adding a virtual clone method and implement it in each derived class like this:

Base* Derived::clone()
{
    return new Derived(*this);
}

Then in a calling code you can:

Base *x = new Derived();
Base *y = x->clone();

However if you have 50+ derived classes and realize you need polymorphic copy, it's tedious to copy-paste the cloning method into each of them. And it's essentially a boilerplate that works around a language limitation that you have to spell out the actual name to call the constructor.

I haven't keep track with the new features in recent C++ standards... Is there a way to avoid this in modern C++?

Calmarius
  • 18,570
  • 18
  • 110
  • 157
  • I don't think c++ support that, but maybe you can write a simple macro, like `GEN_CLONE(Derived)`. – Ziming Song Mar 09 '19 at 10:48
  • 1
    I imagine that such a framework is too difficult to envisage even for the C++ ISO bods who are some of the smartest folk on the planet. A decent cloning pattern is yet to emerge in any language. Look at the dog's dinner that's in Java. A CRTP solution is probably the best of a bad bunch. Facetiously ain't this an "XY" problem? Why do you *really* need to clone stuff? – Bathsheba Mar 09 '19 at 11:05
  • 2
    As far as I'm aware, the normal thing to do here is to type-erase the copy constructor, [as `std::function` does](https://stackoverflow.com/a/38478032/962089). It's certainly possible and this technique is certainly in use. – chris Mar 09 '19 at 11:05
  • 1
    Personally I think any solution is at least as complicated as the actual problem. So I would just bite the bullet and implement your `clone()` functions along with all your other mandatory overrides. Also, your `clone()` function should return `std::unique_ptr`. – Galik Mar 09 '19 at 11:16
  • 1
    @Bathsheba For polymorphic objects you cannot really avoid cloning if you want to copy them in full (not sliced). – Calmarius Mar 09 '19 at 12:09
  • Possible duplicate of [Copying derived entities using only base class pointers, (without exhaustive testing!) - C++](https://stackoverflow.com/questions/5027456/copying-derived-entities-using-only-base-class-pointers-without-exhaustive-tes) – Eric Mar 10 '19 at 04:44

6 Answers6

27

You can use this generic CRTP code

template <class Derived, class Base>
struct Clonable : Base {
    virtual Base* do_clone() {
        return new Derived(*static_cast<Derived*>(this));
    }
    Derived* clone() { // not virtual
        return static_cast<Derived*>(do_clone());
    }

    using Base::Base;
};

struct empty {};
struct A : Clonable<A, empty> {};
struct B : Clonable<B, A> {};

It can be generalised to smart pointers and multiple bases if desired.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Wow, I have never heard of this pattern. I will check if I can use this for my problem. – Calmarius Mar 09 '19 at 12:09
  • 1
    @Calmarius CRTP is a mostly c++ pattern. No wonder if you are used to other languages. – Red.Wave Mar 09 '19 at 16:58
  • @Red.Wave: Outside of C++, it's called "F-bounded polymorphism." You will mostly hear about this in the context of Java's `Comparable` interface. Because C++ templates are fundamentally different from type erasure, the CRTP and F-bounded polymorphism are obviously not exactly the same thing, but in practice they tend to solve the same sorts of problems. – Kevin Mar 10 '19 at 01:32
  • @Kevin didn't think it was that common outside C++. – Red.Wave Mar 10 '19 at 02:31
  • What's the purpose of the `do_clone()` method returning `Base`? πάντα ῥεῖ's answer seems a lot more obviously correct to me – Eric Mar 10 '19 at 04:29
  • @Eric in this scheme one cannot have a virtual `clone` function returning `Derived*`. So it is split into the virtual part (`do_clone`) and the one with the correct return type (`clone`). If you're OK with returning `Base*` then this split is not needed. – n. m. could be an AI Mar 10 '19 at 04:35
  • @n.m.: _"one cannot have a virtual `clone` function returning `Derived*`"_ - is that not exactly what the answer below does? – Eric Mar 10 '19 at 04:38
  • @Eric which one? Cannot seem to find any that does. – n. m. could be an AI Mar 10 '19 at 04:42
  • Huh, you're right, my mistake. [This question](https://stackoverflow.com/q/17201268/102441) I think answers my question - covariant return types and CRTP don't mix. – Eric Mar 10 '19 at 04:47
  • Is there a way to adapt this for a `std::unique_ptr` return type? – davidA Mar 25 '23 at 23:35
  • @davidA Yes, just systematically replace plain pointer constructs with their smart pointer counterparts. – n. m. could be an AI Mar 26 '23 at 03:47
11

You could use a CRTP approach, but that has other drawbacks:

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

template <typename Derived>
class BaseT : public Base {
    // ...
public:
    Base* clone() const override {
        return new Derived(*static_cast<Derived*>(this));
    }
};

Usage:

class DerivedA : public BaseT<DerivedA> {
};

Base *x = new DerivedA();
Base *y = x->clone();

I haven't keep track with the new features in recent C++ standards... Is there a way to avoid this in modern C++?

This trick is available since the c++98 standard.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
2

If you can control how you pass around the polymorphic type, use type erasure. In particular, the proposed std::polymorphic_value calls the derived copy constructor when it is copied. You can imagine it as something like this:

template <typename B>
class polymorphic_value {
public:
    template <typename D,
        std::enable_if_t<
            std::is_base_of<B, std::decay_t<D>>::value, int> = 0>
    explicit polymorphic_value(D&& value)
        : ptr{std::make_unique<derived_t<std::decay_t<D>>>(std::forward<D>(value))}
    {}

    polymorphic_value(polymorphic_value const& rhs)
        : ptr{rhs.ptr->clone()}
    {}

    B const& get() const { return ptr->get(); }

    B& get() {
        // Safe usage of const_cast, since the actual object is not const:
        return const_cast<B&>(ptr->get());
    }

private:
    struct base_t {
        virtual ~base_t() = default;
        virtual std::unique_ptr<B> clone() const = 0;
        // With more effort, this doesn't have to be a virtual function.
        // For example, rolling our own vtables would make that possible.
        virtual B const& get() const = 0;
    };

    template <typename D>
    struct derived_t final : public base_t {
        explicit derived_t(D const& d)
            : value{d}
        {}

        explicit derived_t(D&& d)
            : value{std::move(d)}
        {}

        std::unique_ptr<B> clone() const override {
            return std::make_unique<D>(value);
        }

        B const& get() const override {
            return value;
        }

        D value;
    };

    std::unique_ptr<base_t> ptr;
};

For a thorough implementation which follows the proposal, see jbcoe's github repository.

Sample usage:

class Base {
public:
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    Derived() = default;
    Derived(Derived const&);
};

int main() {
    polymorphic_value<Base> it{Derived{}};
    auto const copy = it;
}

Live on Godbolt

Justin
  • 24,288
  • 12
  • 92
  • 142
1

You can at minimum avoid writing the class name by getting it from the type of the class itself with:

struct A: public Base
{
    Base* Clone() { return new std::remove_reference_t<decltype(*this)>(*this); }
};

Using CRTP will not help avoid duplicating the class name again, as you than have to write the class name inside the template parms for the CRTP base.

Klaus
  • 24,205
  • 7
  • 58
  • 113
1

You probably have a class where you store the polymorphic object and where you want to clone? Together with your polymorphic object you could store a function-pointer doing the cloning:

template<class Derived>
Base* clone(const Base* b) {
    return new Derived(static_cast<const Derived*>(b));
}

void SampleUsage() {
    Base* b = new Derived;
    Base*(*cloner)(const Base*) = clone<Derived>;
    Base* copy = cloner(b);
}

The type of cloner is independent of Derived. Its like a simplified std::function.

Kilian
  • 533
  • 4
  • 11
  • This is actually a good idea, and it's the basis behind _type erasure_. To improve usability, it's a good idea to wrap the `Base*` and the `Base*(*)(const Base*)` function pointer together in a class that automatically does this correctly. – Justin Mar 10 '19 at 04:44
0

You could use CRTP to add an additional layer between your derived class and base that implements your cloning method.

struct Base {
    virtual ~Base() = default;
    virtual Base* clone() = 0;
};

template <typename T>
struct Base_with_clone : Base {
    Base* clone() {
        return new T(*this);
    }
};

struct Derived : Base_with_clone<Derived> {};
super
  • 12,335
  • 2
  • 19
  • 29
  • which shifts the naming of the class from the "new" to the CRTP base class... Did it help? – Klaus Mar 09 '19 at 10:52
  • You don't have to write "new ClassName"... but instead you write `class Derived>` So you simply shift the need of writing the name of the class from the "new" statement to the CRTP one. Helpful? ;) I mention that as OP mention that in his question that he want to avoid naming the derived class name in every new derived class type. – Klaus Mar 09 '19 at 10:55
  • @Klaus Arguably adding `` is a lot less typing then adding a whole cloning method in the class. Indeed a matter of taste, but I would say it could be very helpful. – super Mar 09 '19 at 10:57
  • Agree! It goes *only* to the point that OP can't avoid with CRTP to give the name of the derived class again. Only for that, because it was especially mention by OP. – Klaus Mar 09 '19 at 10:58
  • @Klaus It's not especially mentioned, the only thing mentioned by OP is the fact that he finds it tedious to add a clone method in each derived class. This offers the alternative. – super Mar 09 '19 at 11:00
  • I read ` that you have to spell out the actual name to call the constructor.` in the question ;) – Klaus Mar 09 '19 at 11:01
  • @Klaus So you choose to interpret that very freely as "I want a solution that does not have to repeat the name of the derived class in any way", and pass on this constructive information in the comments to me. Seems like your input in not helping to improve the quality of the answer in the least. – super Mar 09 '19 at 11:06