【问题标题】:Cleanly duplicate an instance of a baseclass or subclass in C++?在 C++ 中干净地复制基类或子类的实例?
【发布时间】:2010-10-23 13:18:37
【问题描述】:

在简单的继承层次结构示例中:

class Food
{
    virtual ~Food();
};

class Fruit : public Food
{
    virtual ~Fruit();
};

class Apple: public Fruit
{
    virtual ~Apple();
}

class Vegetable: public Food
{
    virtual ~Vegetable();
}

我希望创建一个可以从其子类或基类实例中克隆对象的方法:

Apple* apple1 = new Apple();
Apple* clone1 = apple1->clone();

Food* food1 = apple1;
Apple* clone2 = food1->clone();

我看到了一些可能的解决方案:

  • 使用多态性创建为每个子类明确定义的虚函数。

  • 使用模板化工厂方法获取类的实例和子类的类型来创建新实例。

  • 为存储在别处的每个类和子类注册一个 id,以便在调用基类克隆函数时查找要创建的子类。

这些似乎都不理想,但我更倾向于第三种解决方案,因为它简化了对克隆函数的调用,而无需为每个子类编写定义(会有很多子类)。

但是,我非常愿意接受任何建议,有没有更好的方法来做到这一点?

【问题讨论】:

    标签: c++ templates refactoring polymorphism factory


    【解决方案1】:

    您可以使用 CRTP 自动实现 Clone 方法。

    template<typename T, typename Derive> class CloneImpl : public Derive {
    public:
        virtual Derive* clone() {
            return new T(static_cast<const T&>(*this));
        }
    };
    class Food {
    public:
        virtual Food* clone() = 0;
        virtual ~Food() {}
    };
    class Fruit : public Food {
    };
    class Dairy : public Food {
    };
    class Apple : public CloneImpl<Apple, Fruit> {
    };
    class Banana : public CloneImpl<Banana, Fruit> {
    };
    class Cheese : public CloneImpl<Cheese, Dairy> {
    };
    class Milk : public CloneImpl<Milk, Dairy> {
    };
    

    在这种情况下,您始终可以调用 Clone() 以在堆上使用新分配复制当前对象,而无需在任何类中再次实现它。当然,如果您的 Clone 语义需要不同,那么您只需更改函数即可。

    CRTP 不仅可以为您实现 clone(),它甚至可以在不同的继承层次之间实现。

    【讨论】:

    • @DeadMG:您的解决方案似乎不适用于 OP 的类层次结构。您能否展示您如何解决该层次结构的问题,Food
    • @Alf:你说得对,我错过了水果中介课程,所以我会编辑。
    • @DeadMG:在 OP 的示例中,这些是(显然)具体的类。现在它们是抽象的。需要克隆的层次结构具体类的更具体的实践示例(在 C++98 中)是异常类层次结构。那么,您是否也可以将课程具体化?所以所有的类都是可克隆的?干杯,
    • @Alf:用涉及非抽象类的卓越解决方案编辑了我的答案。
    • @Steve Jessop:包含该步骤的我的编辑是在您发表评论前三分钟发布的。思考时间长吗? :P
    【解决方案2】:

    你的最后一个例子,...

    Food* food1 = dynamic_cast<Food*>(apple1);
    Apple* clone2 = f1->clone();
    

    ... 不会工作,即使拼写错误已更正。你需要另一种方式:

    Food* food1 = apple1;
    Apple* clone2 = dynamic_cast<Apple*>( f1->clone() );
    

    除此之外,在 C++ 中克隆的实际解决方案是定义一个宏:

    #define YOURPREFIX_IMPLEMENT_CLONING( Class )                       \
        virtual Class*                                                  \
            virtualCloneThatIsUnsafeToCallDirectly() const              \
        {                                                               \
            assert( typeid( *this ) == typeid( Class ) );               \
            return new Class( *this );                                  \
        }                                                               \
                                                                        \
        OwnershipPtr< Class >                                           \
            clone() const                                               \
        {                                                               \
            return OwnershipPtr< Class >(                               \
                virtualCloneThatIsUnsafeToCallDirectly()                \
                );                                                      \
        }
    

    ...OwnershipPtr 可能是例如std::auto_ptr.

    那么你所要做的就是在每个应该支持克隆的类中放置一个宏调用。很简单。

    也可以通过模板实现可重复使用的克隆,但无论是实现还是使用都比较复杂。您可以在我的博客帖子"3 ways to mix in a generic cloning implementation" 中了解这一点。然而,结论是宏是最实用的。

    【讨论】:

    • 不,实际的解决方案肯定不是宏。
    • @DeadMG,你有什么支持这种观点的论据吗?例如。使用更干净实用的东西?我最感兴趣,整个 C++ 社区也是如此。 :-) 干杯&hth.,
    • @DeadMG,PS,如果您无法提出更实用的建议,请删除反对票。 TIA.,
    • 我只是发布我自己的解决方案。回想起来,我并不完全确定投反对票是正确的选择。但是,我不能删除它,也不能支持你。另外,出于某种原因,除非您进行编辑,否则我无法重新考虑。这是以前从未发生过的。
    • 如果您编辑您的帖子(最好是一些琐碎的内容),那么我可以撤消我的反对意见。
    猜你喜欢
    • 2017-12-19
    • 2023-03-08
    • 1970-01-01
    • 2015-02-04
    • 2012-03-11
    • 2017-04-10
    • 1970-01-01
    • 2012-11-28
    • 1970-01-01
    相关资源
    最近更新 更多