【问题标题】:How do you deep copy a vector of derived class pointers via the copy constructor?如何通过复制构造函数深度复制派生类指针的向量?
【发布时间】:2021-06-27 21:14:13
【问题描述】:
#include <iostream>
#include <string>
#include <vector>
//#include "V2d.h"
using namespace std;

class item {
public:
    int value;
    string description;
    item() {}
    virtual ~item() {}
    virtual void display() {
        cout << "Value: " << value << endl;
    }
};

class Cat : public item {
public:
    string name = "Cat";
    Cat() {
        value = 20;
        description = "a cat";
    }
    void display() {
        cout << "Name: " << name << endl;
        cout << "Value: " << value << endl;
        cout << "Description: " << description << "\n" << endl;
    }
};

class Dog : public item {
public:
    string name = "Dog";
    Dog() {
        value = 10;
        description = "a dog";
    }
    void display() {
        cout << "Name: " << name << endl;
        cout << "Value: " << value << endl;
        cout << "Description: " << description << "\n" << endl;
    }
};

class v2d {
public:
    int hitPoints;

    enum ItemName {
        APPLE, ORANGE
    };
    vector<item*> inventory;
    v2d() {
    }
    ~v2d() {
        for (int i = 0; i < inventory.size(); i++) {
            delete inventory[i];
        }

        inventory.clear();

    }
    v2d(const v2d& orig) : inventory(orig.inventory.size()) {

        hitPoints = orig.hitPoints;
        for (int i = 0; i < inventory.size(); ++i) {
            inventory[i] = new item(*orig.inventory[i]);
        }
        cout << "Copied!" << endl;
    }
    void display() {
        for (int i = 0; i < inventory.size(); i++) {
            inventory[i]->display();
        }
    }

};
int main() {
    v2d vect1;
    vect1.inventory.push_back(new Cat());
    vect1.inventory.push_back(new Dog());
    vect1.inventory.push_back(new Dog());
    vect1.inventory.push_back(new Cat());
    vect1.inventory.push_back(new Dog());
    vect1.display();

    cout << "**************************\n" << endl;

    v2d vect2(vect1);
    vect2.display();
}

我应该从哪里开始尝试重载 v2d 中的 += 和 -= 运算符以减去和添加到库存向量? 我已经完成了拥有基类和派生类的部分;我似乎在使用动态转换时遇到了麻烦不。有没有一种简单的方法可以通过复制构造函数深度复制派生类指针的向量,而不使用 clone() 函数?

【问题讨论】:

  • 您可以使用每个派生类都可以覆盖的虚拟clone 方法。此外,请考虑使用智能指针而不是原始指针。
  • delete inventory[i]; -- 你的 item 类没有虚拟析构函数。因此,如果要删除的指针是从item 派生的,那么这行代码将调用未定义的行为。另外,考虑一下:v2d vect1; Dog d; vect1.inventory.push_back(&amp;d);——这也会调用未定义的行为,因为d 没有分配给new。

标签: c++ pointers containers


【解决方案1】:

要正确执行此操作,还需要向基类添加两件事:

class item {
public:

    virtual ~item() {}

基类必须有一个虚析构函数。

    virtual item *clone() const=0;

还有一个传统上称为clone() 的抽象方法。您的每个子类都必须实现clone(),通常使用复制构造函数:

class Cat : public item {
public:

    item *clone() const override { return new Cat{*this}; };

对item 的所有其他子类执行相同的操作。然后,有了这个,您就可以正确地克隆这些对象的向量:

    for (int i = 0; i < inventory.size(); ++i) {
        inventory[i] = orig.inventory[i]->clone();
    }

【讨论】:

    【解决方案2】:

    我似乎在不使用动态转换时遇到了麻烦。有没有一种简单的方法可以通过复制构造函数深度复制派生类指针的向量,而不使用 clone() 函数?

    clone 是传统的解决方案,但我会提出一个替代方案。

    考虑使用类型擦除和值类型。 到目前为止,它将提供最简单的用法,但设置起来却更加复杂。值类型通常可以更清晰地与许多语言和库进行互操作。它们的侵入性较小。标准类型可以满足您的接口。不需要继承。值类型在任何地方都不需要指针和间接寻址。这些只是其中的一小部分优势。

    在 C++20 中,我们可以为 item 定义一个概念,它是另一个名称的接口:

    template <typename Item>
    concept item = std::copyable<Item> && requires(const Item& const_item, Item& item) {
        { item.value() } -> std::same_as<int&>;
        { const_item.value() } -> std::same_as<const int&>;
        { item.description() } -> std::same_as<std::string&>;
        { const_item.description() } -> std::same_as<const std::string&>;
        { item.display() };
        { const_item.display() };
    };
    

    在 C++20 之前,概念通常是隐含的或记录在案的,但不是代码中的实体。但在 C++20 中,我们可以定义它们。请注意,此公式需要std::copyable,因此任何满足此要求的item 都可以使用标准复制构造函数进行复制。另外请注意,接口中的原始数据成员会使事情稍微复杂化,因此我将它们替换为访问器,同时仍然允许问题中的代码允许的公共读写访问。

    定义了接口后,就可以定义一个类型擦除的版本:定义一个满足item 的类型any_item,它本身可以保存满足item 的任何类型的值。您可能会发现许多关于类型擦除的在线资源,以及所涉及的选择。关键是要有对象的存储空间,以及接口的函数指针表(vtable)。可以将对象存储在堆上,也可以为小对象提供一个小的内部缓冲区。可以内联存储 vtable 或存储指向 vtable 的指针。可以显式地编写 vtable,或者依靠隐藏的类层次结构来强制编译器编写 vtable。甚至可以依靠一个库(例如 dyno)为您完成这项工作。

    请注意,所有这些复杂性都由接口或库作者处理。用户不必继承任何东西。用户不必定义样板文件clone 函数。用户只需定义概念所需的功能。然后用户可以使用类型擦除类型,并像使用任何其他值类型一样使用它。用户可以将它们放在std::vector 中,然后观察复制构造函数是否正常工作。

    代码如下:

    std::vector<item*> inventory;
    
    ~v2d()
    {
        for (auto* item: inventory) {
            delete item;
        }
    }
    
    v2d(const v2d& orig) :
        inventory(orig.inventory.size())
    {
        for (int i = 0; i < inventory.size(); i++) {
            inventory[i] = new item(*orig.inventory[i]);
        }
        cout << "Copied!" << endl;
    }
    
    vect1.inventory.push_back(new Cat());
    

    变成:

    // just use values
    std::vector<any_item> inventory;
    
    // destruction just works
    ~v2d() = default;
    
    // copy just works
    v2d(const v2d& other) :
        inventory(other.inventory)
    {
        std::cout << "Copied!\n";
    }
    
    // just use values, again
    vect1.inventory.push_back(cat());
    

    See an example

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-17
      • 2013-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-07
      相关资源
      最近更新 更多