【问题标题】:Copying a Polymorphic object in C++在 C++ 中复制多态对象
【发布时间】:2011-07-06 03:26:48
【问题描述】:

我有基类Base 派生自Derived1Derived2Derived3

我已经为我存储为Base* a 的派生类之一构建了一个实例。我现在需要制作对象的深层副本,并将其存储为Base* b

据我所知,复制类的正常方法是使用复制构造函数并重载operator=。但是,由于我不知道a 的类型是Derived1Derived2 还是Derived3,所以我想不出使用复制构造函数或operator= 的方法。我能想到干净地完成这项工作的唯一方法是实现类似的东西:

class Base
{
public:
  virtual Base* Clone() = 0;

};

并在派生类中实现Clone,如下所示:

class Derivedn : public Base
{
public:
  Base* Clone() 
  {
    Derived1* ret = new Derived1;
    copy all the data members
  }
};

Java 倾向于使用 Clone 相当多,是否有更多的 C++ 方式来执行此操作?

【问题讨论】:

  • 执行此操作的最佳方式可能取决于大局,您尝试执行此操作的原因以及在何种情况下...
  • 我有一个由不同对象组成的复杂树,有些是多态的。我想使用递归算法复制树。
  • 希望对您有所帮助:stackoverflow.com/questions/3831370/…。按照“这个、这个和这个”中提到的句子中的链接进行操作
  • 可克隆模式有一些您需要注意的重要属性。这篇文章值得一读:herbsutter.com/2019/10/03/…

标签: c++ clone


【解决方案1】:

我已经看到一些使用模板函数克隆对象的答案。让我告诉你这是如何行不通的。考虑以下代码:

这是一种特殊情况,当从基础对象容器接收对象时会出现这种情况。即使 obj 是 Derived 类型,该函数也会返回一个指向 Base 的指针。该模板仅在被未经过任何强制转换的对象调用时才有效。

#include <iostream>
#include <memory>
#include <vector>

class Base{
public:
    virtual void foo(){}
};

class Derived : public Base{};

template<typename T>  std::shared_ptr<T> foo(const T& obj){
    std::cout << "obj is of type: " << typeid(obj).name() << std::endl;
    std::cout << "T is of type: " << typeid(T).name() << std::endl;
    std::cout << std::endl;
    return std::make_shared<T>(obj); // returns Base pointer
}

int main()
{
    std::vector<std::shared_ptr<Base>> vec {std::make_shared<Base>(), std::make_shared<Derived>()};
    for(auto c: vec)
        foo(*c);

    return 0;
}

/* OUTPUT:
obj is of type: 4Base
T is of type: 4Base

obj is of type: 7Derived
T is of type: 4Base

*/

【讨论】:

    【解决方案2】:

    这仍然是我们在 C++ 中为多态类做事的方式,但如果为对象创建复制构造函数(可能是隐式或私有的),则不需要显式复制成员。

    class Base
    {
    public:
      virtual Base* Clone() = 0;
    };
    
    class Derivedn : public Base
    {
    public:
      //This is OK, its called covariant return type.
      Derivedn* Clone() 
      {
        return new Derivedn(*this);
      }
    private:
      Derivedn(const Derivedn&) : ... {}
    };
    

    【讨论】:

    • Clone() 不必是抽象的。如果子类共享公共数据,则基类可能有一个复制构造函数来复制公共部分,并有一个虚拟复制构造函数来处理复制变体部分。无论采用哪种方式,每个派生类都需要自己的克隆方法。
    • 这里为什么使用协变返回类型? Base* Derivedn::Clone() { return new Derivedn(*this); } 不是同样好用吗?
    • 在您出于某种原因需要访问 Derived 之前,它确实可以正常工作 - 在这里使用协变返回没有任何成本,以后可能会有所帮助。 (而且我当然发现在使用这种模式时它很有用)
    • 这是很多 biolerplaite 代码。必须为每个派生类写出 Clone 函数。有没有办法分配将在运行时指定的类的新实例?
    • 所以必须为每个子类实现完全相同的方法?这太恶心了:(
    【解决方案3】:
    template <class T>
    Base* Clone (T derivedobj) {
      T* derivedptr = new T(derivedobj);
      Base* baseptr = dynamic_cast<Base*>(derivedptr);
      if(baseptr != NULL) {
        return baseptr;
      }
      // this will be reached if T is not derived from Base
      delete derivedptr;
      throw std::string("Invalid type given to Clone");
    }
    

    这个函数对派生类的唯一要求是它们的复制构造函数是可公开访问的。

    【讨论】:

    • 但是不能多态调用。该参数是按值传递的,在任何多态使用中都会导致切片。
    • 这段代码有更多错误。 Clone 正在接受它的参数 by value 这意味着你已经调用了它的复制构造函数!此函数及其dynamic_cast 并在它是错误类型时退出,与Base* y = new&lt;T&gt;(derivedobj); 等效(在它工作的情况下)但这里的错误(T 不是从Base 派生的)是在编译时发现。这根本不能解决 OP 的问题,因为您必须知道实际的 T 并且在使用它之前还要将原始指针向下转换回它的实际类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-06
    • 2018-07-08
    • 1970-01-01
    • 1970-01-01
    • 2019-01-24
    • 1970-01-01
    相关资源
    最近更新 更多