是否可以克隆多态对象而无需在C ++中手动将重写的克隆方法添加到每个派生类中?


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

当您想要复制多态类时,典型的模式是添加一个虚拟克隆方法并在每个派生类中实现它,如下所示:

1
2
3
4
Base* Derived::clone()
{
    return new Derived(*this);
}

然后在呼叫代码中,您可以:

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

但是,如果您有50多个派生类,并且意识到您需要多态复制,那么将克隆方法复制粘贴到每个派生类中是很麻烦的。它本质上是一个围绕语言限制工作的样板文件,您必须拼写出调用构造函数的实际名称。

我没有跟上最近的特点,在最近的C++标准…在现代C++中有没有办法避免这种情况?


您可以使用这个通用的CRTP代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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> {};

它可以概括为智能指针和多个基础,如果需要的话。


您可以使用CRTP方法,但它还有其他缺点:

1
2
3
4
5
6
7
8
9
10
11
12
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));
    }
};

用途:

1
2
class DerivedA : public BaseT<DerivedA> {
};
1
2
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++?

这个技巧是从C++ 98标准中获得的。


如果可以控制如何传递多态类型,请使用类型擦除。特别是,当复制时,建议的std::polymorphic_value调用派生的复制构造函数。你可以把它想象成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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 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 clone() const override {
            return std::make_unique<D>(value);
        }

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

        D value;
    };

    std::unique_ptr<base_t> ptr;
};

有关遵循建议的全面实现,请参阅jbcoe的Github存储库。

样品使用情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

活在上帝的手中


您可能有一个类,在其中存储多态对象,并在其中克隆?与多态对象一起,您可以存储执行克隆的函数指针:

1
2
3
4
5
6
7
8
9
10
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);
}

cloner的类型独立于派生类型。它类似于一个简化的std::函数。


从类本身的类型获取类名时,至少可以使用以下方法避免写入类名:

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

使用crtp并不能避免再次复制类名,因为您需要在crtp基的模板参数中写入类名。


可以使用CRTP在派生类和实现克隆方法的基之间添加一个附加层。

1
2
3
4
5
6
7
8
9
10
11
12
13
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> {};