Как вы глубоко копируете вектор указателей производных классов с помощью конструктора копирования?

#c #pointers #containers

Вопрос:

 #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 v2damp; 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 ()?

Комментарии:

1. Вы можете использовать виртуальный clone метод, который может переопределять каждый производный класс. Кроме того, подумайте об использовании интеллектуальных указателей вместо необработанных указателей.

2. delete inventory[i]; — У вашего item класса нет виртуального деструктора. Таким образом, эта строка кода вызовет неопределенное поведение, если удаляемый указатель является производным от item . Кроме того, подумайте об этом: v2d vect1; Dog d; vect1.inventory.push_back(amp;d); — Это также вызовет неопределенное поведение, поскольку d не new было выделено с.

Ответ №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> amp;amp; requires(const Itemamp; const_item, Itemamp; item) {
    { item.value() } -> std::same_as<intamp;>;
    { const_item.value() } -> std::same_as<const intamp;>;
    { item.description() } -> std::same_as<std::stringamp;>;
    { const_item.description() } -> std::same_as<const std::stringamp;>;
    { item.display() };
    { const_item.display() };
};
 

До C 20 концепции, как правило, были неявными или документированными, но не сущностями в коде. Но в C 20 мы можем их определить. Обратите внимание, что эта формулировка требует std::copyable , чтобы все, item удовлетворяющее этому, было доступно для копирования с использованием стандартного конструктора копирования. Кроме того, обратите внимание, что элементы необработанных данных в интерфейсе немного усложняют ситуацию, поэтому я заменил их средствами доступа, по-прежнему предоставляя общедоступный доступ для чтения и записи, который позволяет код в вопросе.

С определенным интерфейсом можно затем определить версию со стиранием типа: определить any_item удовлетворяющий тип item , который сам может содержать значение любого удовлетворяющего типа item . Вы, вероятно, найдете много онлайн-источников об удалении типов и связанных с этим вариантах. Ключ должен иметь хранилище для объекта и таблицу указателей функций для интерфейса (виртуальная таблица). Можно хранить объект в куче или иметь небольшой внутренний буфер для небольших объектов. Можно сохранить встроенную таблицу vtable или сохранить указатель на таблицу vtable. Можно написать vtable явно или полагаться на скрытую иерархию классов, чтобы принудить компилятор написать ее. Можно даже положиться на библиотеку (например, dyno), чтобы сделать это за вас.

Обратите внимание, что вся эта сложность решается автором интерфейса или библиотеки. Пользователю не нужно ничего наследовать. Пользователю не нужно определять стандартные clone функции. Пользователю нужно только определить функции, требуемые концепцией. Затем пользователь может использовать тип, стираемый типом, и использовать его как любой другой тип значения. Пользователь может поместить их в a std::vector и наблюдать, как конструктор копирования просто работает.

Код, который выглядел как:

 std::vector<item*> inventory;

~v2d()
{
    for (auto* item: inventory) {
        delete item;
    }
}

v2d(const v2damp; 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() = defau<

// copy just works
v2d(const v2damp; other) :
    inventory(other.inventory)
{
    std::cout << "Copied!n";
}

// just use values, again
vect1.inventory.push_back(cat());
 

Смотрите пример