Is it possible to clone a polymorphic object without manually adding overridden clone method into each derived class in C++?

后端 未结 6 2078
迷失自我
迷失自我 2020-12-30 01: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* Derive         


        
相关标签:
6条回答
  • 2020-12-30 01:30

    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.

    0 讨论(0)
  • 2020-12-30 01:30

    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> {};
    
    0 讨论(0)
  • 2020-12-30 01:41

    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

    0 讨论(0)
  • 2020-12-30 01:46

    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.

    0 讨论(0)
  • 2020-12-30 01:51

    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.

    0 讨论(0)
  • 2020-12-30 01:52

    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.

    0 讨论(0)
提交回复
热议问题