首页 文章

带有智能指针附加的继承对象的深层复制

提问于
浏览
0

我不确定使用包含继承对象的智能指针对对象进行深层复制的最佳/最干净的解决方案是什么 . 将它煮沸,给出以下代码

class A {};

class D1 : public A{
public:
    int x1 = 0;
};

class D2 : public A {
public:
    int x2 = 2;
};


class V {
public:
    V(A* a) : ptr(a) {}
    std::unique_ptr<A> ptr;
};    

void run() {
    std::vector<V> v;
    v.push_back(V(new D1));
    v.push_back(V(new D2));
    /// I want to make a deep copy of v here
}

其中vector v 包含 D1D2 类型的对象,是什么是制作 v 的深层副本的最短/最优雅的方法?我可以想到两种方法,都有一些缺点:

  • 在基类中创建一个虚拟 A* clone() 方法,并在每个继承的类中重载它(如here所述) . 缺点:克隆方法需要在每个继承的类中实例化,并且可能有多个 .

  • V 创建复制构造函数/赋值运算符 . 使用 dynamic_cast<D1/D2> ,检查附加了哪种继承对象,并为该特定类型制作副本 . 缺点:需要遍历 V 的复制构造函数中的所有继承类 .

2 回答

  • 1

    每次将类添加到 A 下的层次结构中时,选项1都不需要您修改 V . 此外,如果添加的类没有实现 clone ,您将获得一个漂亮的闪亮编译器错误,而不是像运行时一样在运行时构建和失败 .

    因此选项1更好 . 但你是对的,它有点重复 . 您必须为许多不同类型编写类似的代码 . 幸运的是,C有一个处理它的机制:模板 .

    使用CRTP类,我们可以自动实现 clone 函数 . 所有 D1D2 需要做的是从中间人继承,而不是直接从 A 继承:

    class A {
    public:
      virtual A* clone() const = 0;
      virtual ~A() = default;
    };
    
    template<class C>
    struct AClone : A {
      A* clone() const override {
        return new C(*static_cast<C const*>(this));
      }
    };
    
    class D1 : public AClone<D1> {
    public:
        int x1 = 0;
    };
    
    class D2 : public AClone<D2> {
    public:
        int x2 = 2;
    };
    

    以上是使用原始指针,并且可能会通过返回 unique_ptr 来改进,但为了简洁起见,这个想法已经归结 .

    人们还可以为这个 clone 函数添加一些防御性编程 .

    static_assert(std::is_convertible<C*, A*>::value,"");
    static_assert(std::is_convertible<C*, AClone*>::value,"");
    // These two check `C` is derived unambiguasly from `A` via this specialization
    
    assert(typeid(C) == typeid(*this));
    // Check the most derived type is as expected, suggested by Deduplicator
    

    你可以看到它,here .

  • 3

    好吧,让我们来看看它:

    • A 没有虚拟dtor,所以当成员 unique_ptr 尝试多态破坏ist指针时, V 的dtor会调用UB .

    • dynamic_cast 只能用于检查派生最多的类型(如果它实际上是 final ),并且源类型具有虚拟方法和/或基础 . 虽然您似乎不是从 D1 和/或 D2 派生的,但's nothing stopping anyone else from doing so. And you don' t有任何虚拟基础或方法 . 至少使用 typeid 并添加虚拟dtor .

    • 使用virtual .clone() 允许您省略所有繁琐且容易出错的类型检查,并在扩展到新类时包含任何必要的更改 . 另一种方法是使用映射进行注册,将指针存储在克隆方法旁边,或者将其全部写为代码 .

相关问题