【问题标题】:How to clone as derived object in C++如何在 C++ 中克隆为派生对象
【发布时间】:2014-09-16 08:30:15
【问题描述】:

我在 C++ 中定义了两个类。一个是基类,一个是派生类

    class CBaseClass
    {
    …
    }

    class CDerivedClass : public CBaseClass
    {
    …
    }

并且想实现一个克隆功能如下:

    CBaseClass *Clone(const CBaseClass *pObject)
    {
    }

当 CDerivedClass 的对象被传递给 Clone 时,该函数也会创建一个 CDerivedClass 对象并返回。 当 CBaseClass 的对象传递给 Clone 时,该函数也会创建一个 CBaseClass 对象并返回。

如何实现这样的功能?

【问题讨论】:

标签: c++ clone


【解决方案1】:

您可以使用虚拟 Clone 方法和帮助模板 CRTP 类来实现此接口:

class CBaseClass {
    //...
    virtual CBaseClass * Clone () = 0;
    std::unique_ptr<CBaseClass> UniqueClone () {
        return std::unique_ptr<CBaseClass>(Clone());
    }
    virtual std::shared_ptr<CBaseClass> SharedClone () = 0;
};

template <typename DERIVED>
class CBaseClassCRTP : public CBaseClass
{
    CBaseClass * Clone () {
        return new DERIVED(*static_cast<DERIVED *>(this));
    }
    std::shared_ptr<CBaseClass> SharedClone () {
        return std::make_shared<CbaseClass>(*static_cast<DERIVED *>(this));
    }
};

class CDerivedClass : public CBaseClassCRTP<CDerivedClass>
{
    //...
};

现在,每个派生类都获得了一个由辅助类提供的Clone 方法。

【讨论】:

  • 如果你有一个 Base 指针并且不知道确切的类型会发生什么?你应该返回DERIVED *
  • dynamic_cast 是不必要的,您应该在这里使用智能指针。
  • 将要克隆的对象传递给克隆函数似乎很奇怪。我习惯于像这样调用克隆:cloned_object = some_object.clone()
  • 看起来很奇怪,您要克隆的东西将由std::unique_ptr 管理;克隆后大概就完成了,不需要任何进一步的管理。
【解决方案2】:

这是一个简单的解决方案。请记住为继承中的每个类提供一个 Clone。

class Base
{
public:
    virtual ~Base() {}
    virtual Base *Clone() const
    {
        // code to copy stuff here
        return new Base(*this);
    }
};

class Derived : public Base
{
public:
    virtual Derived *Clone() const
    {
        // code to copy stuff here
        return new Derived(*this);
    }
};

【讨论】:

  • @Puppy Covariant return 不适用于 unique_ptr 所以我放弃了。如果您有更好的解决方案,请告知。
  • @NeilKirk:你唯一需要克隆的时候是你不知道派生类型最多的时候,所以简单地返回一个我能想到的std::unique_ptr&lt;Base&gt; 没有缺点。跨度>
  • 这里使用协变返回有什么好处?
  • @Puppy 为什么在克隆时你会使用除了原始指针之外的任何东西?为什么你会用 CRTP 超载作品?第一个可能是设计错误,CRTP 只是不必要的额外复杂性。
  • 我同意 Mooing Duck 的观点,unique_ptr&lt;Base&gt; 应该没有问题,但是如果协变返回很重要,您可以创建一个 make 样式,类似于 make_unique,工厂立即将其绑定到所需的unique_ptr
【解决方案3】:

虚拟克隆模式通常用于解决诸如此类的问题。经典解决方案倾向于为 clone() 方法使用协变返回类型。其他解决方案在基类和派生类之间注入工厂类型类(使用 CRTP)。甚至有一些解决方案只使用宏来实现此功能。请参阅 C++ FAQC++ idiomsblog on this。这些解决方案中的任何一种都是可行的,最合适的将取决于它们的使用环境和预期使用环境。

使用covariant return types 并结合更现代的RAII 技术(shared_ptr 等人)的经典方法提供了非常灵活和安全的组合。协变返回类型的优点之一是您可以在层次结构中获得与参数相同级别的克隆(即返回并不总是返回到基类)。

该解决方案确实需要访问shared_ptr 和/或unique_ptr。如果您的编译器不可用,boost 会为这些提供替代方案。 clone_sharedclone_unique 以标准库中相应的 make_sharedmake_unique 实用程序为模型。它们包含对参数和目标类型的类层次结构的显式类型检查。

#include <type_traits>
#include <utility>
#include <memory>

class CBaseClass {
public:
  virtual CBaseClass * clone() const {
    return new CBaseClass(*this);
  }
};

class CDerivedClass : public CBaseClass {
public:
  virtual CDerivedClass * clone() const {
    return new CDerivedClass(*this);
  }
};

class CMoreDerivedClass : public CDerivedClass {
public:
  virtual CMoreDerivedClass * clone() const {
    return new CMoreDerivedClass(*this);
  }
};

class CAnotherDerivedClass : public CBaseClass {
public:
  virtual CAnotherDerivedClass * clone() const {
    return new CAnotherDerivedClass(*this);
  }
};

// Clone factories

template <typename Class, typename T>
std::unique_ptr<Class> clone_unique(T&& source)
{
  static_assert(std::is_base_of<Class, typename std::decay<decltype(*source)>::type>::value,
    "can only clone for pointers to the target type (or base thereof)");
  return std::unique_ptr<Class>(source->clone());
}

template <typename Class, typename T>
std::shared_ptr<Class> clone_shared(T&& source)
{
  static_assert(std::is_base_of<Class, typename std::decay<decltype(*source)>::type>::value,
    "can only clone for pointers to the target type (or base thereof)");
  return std::shared_ptr<Class>(source->clone());
}

int main()
{
  std::unique_ptr<CDerivedClass> mdc(new CMoreDerivedClass()); // = std::make_unique<CMoreDerivedClass>();
  std::shared_ptr<CDerivedClass> cloned1 = clone_shared<CDerivedClass>(mdc);
  std::unique_ptr<CBaseClass> cloned2 = clone_unique<CBaseClass>(mdc);
  const std::unique_ptr<CBaseClass> cloned3 = clone_unique<CBaseClass>(mdc);
  // these all generate compiler errors
  //std::unique_ptr<CAnotherDerivedClass> cloned4 = clone_unique<CAnotherDerivedClass>(mdc);
  //std::unique_ptr<CDerivedClass> cloned5 = clone_unique<CBaseClass>(mdc);
  //auto cloned6 = clone_unique<CMoreDerivedClass>(mdc);
}

我添加了 CMoreDerivedClassCAnotherDerivedClass 来稍微扩展层次结构,以更好地显示类型检查等。

Sample code

【讨论】:

  • 您需要调用make_shared 以避免克隆共享指针时的内存分配开销。
  • @jxh,好点子。 make_shared 虽然创建了对象本身,这也是 clone 方法所做的;因此发生冲突,因为clone_shared 方法实际上并不知道其参数的派生最多的类型是什么(仅知道目标参数之间的关系)。我认为这仍然是可能的,但我觉得它超出了问题的范围。我认为clone_unique 几乎总是更有意义,但代码不限于此。
  • 了解派生类是 CRTP 提供的优势。我同意这两种方法都不能提供完美的解决方案。
猜你喜欢
  • 2011-05-15
  • 2011-02-04
  • 2012-12-15
  • 1970-01-01
  • 2017-07-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多