Как обернуть список указателей на абстрактный класс?

#c #pointers #polymorphism #abstract-class #smart-pointers

#c #указатели #полиморфизм #абстрактный класс #интеллектуальные указатели

Вопрос:

Я пытаюсь обернуть список интеллектуальных указателей на абстрактный класс ( list<shared_ptr<Base>> list_ ) в некоторые классы ( Item , Drawer , Box ). Затем в основной функции у меня есть map of Box ‘es, и это не работает. Я нашел обходной путь, и я могу использовать new но я подозреваю, что это просто вызывает ошибки, которые я не вижу. Как заставить это работать? Вот код:

 #include <iostream>
#include <list>
#include <map>
#include <memory>
using namespace std;

class Base {
public:
    virtual int get() = 0;
};

class Derived : public Base {
public:
    Derived(int x) { x_ = x; }
    int get() override { return x_; }
private:
    int x_;
};

class Item {
public:
    Item() {
        for (int i = 1; i <= 10; i  ) {
            list_.push_back(make_shared<Derived>(i));
        }
    }
    list<shared_ptr<Base>>amp; get_list() { return list_; }
private:
    list<shared_ptr<Base>> list_;
};

class Drawer {
public:
    Drawer(Itemamp; item) : item_(item) {}
    void Draw() {
        list<shared_ptr<Base>>amp; list = item_.get_list();
        cout << list.size() << ":  ";
        while (!list.empty()) {
            shared_ptr<Base> pointer = dynamic_pointer_cast<Derived>(list.front());
            cout << pointer->get() << " ";
            list.pop_front();
        }
        cout << endl;
    }
private:
    Itemamp; item_;
};

class Box {
public:
    Box() : drawer_(item_) {}
    void Draw() { drawer_.Draw(); }
private:
    Item item_;
    Drawer drawer_;
};

int main() {
    Box box;
    box.Draw();

    map<int, Box> boxes;                                // it doesn't work, why?
    for (int i = 0; i < 3; i  ) {
        boxes.insert(std::pair<int, Box>(i, Box()));
    }
    for (autoamp; b : boxes) { b.second.Draw(); }

    map<int, Box*> pointers;                            // it does work, why?
    for (int i = 0; i < 3; i  ) {
        pointers.insert(std::pair<int, Box*>(i, new Box()));
    }
    for (autoamp; b : pointers) {  b.second->Draw(); }
    for (autoamp; b : pointers) {  delete b.second; }
}
  

И вот результат:

 10:  1 2 3 4 5 6 7 8 9 10
0:
0:
0:
10:  1 2 3 4 5 6 7 8 9 10
10:  1 2 3 4 5 6 7 8 9 10
10:  1 2 3 4 5 6 7 8 9 10
  

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

1. Дезинфицирующее средство для адреса — ваш друг. Это сразу показывает, что программа делает что-то дурацкое.

Ответ №1:

В этой строке здесь

 boxes.insert(std::pair<int, Box>(i, Box()));
  

Вы создаете временный Box объект в своей паре, который перемещается на карту.

Давайте назовем их Box1 создаваемым временным объектом и Box2 объектом, созданным для перемещения внутри карты.

При Box1 создании у него правильно есть ящик, который ссылается на элемент в Box1 .

Когда мы затем перемещаем его на карту, мы получаем, Box2 у которого есть ящик, который все еще ссылается на элемент в Box1 .

Когда мы затем продолжим

 for (autoamp; b : boxes) { b.second.Draw(); }
  

Box1 уже уничтожен и больше не существует. Поэтому, когда мы пытаемся использовать ссылку на него, мы используем висячую ссылку, которая является UB. В этом случае вы получите результат 0, но вы также можете получить сбой или любой случайный вывод.

Чтобы исправить это, мы можем добавить конструктор копирования в Box , чтобы справиться с этим.

 class Box {
public:
    Box() : drawer_(item_) {}
    Box(const Boxamp; other) : item_(other.item_), drawer_(item_) {}
    void Draw() { drawer_.Draw(); }
private:
    Item item_;
    Drawer drawer_;
};
  

Теперь ящик копии будет ссылаться на нужный элемент.

Что касается того, почему версия с указателями работает, это потому, что мы копируем указатель, поэтому тот же объект остается до тех пор, пока он не будет удален. Объект не перемещается и не копируется, копируется только указатель, и скопированный указатель по-прежнему ссылается на нужный объект.

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

1. Хорошо замечено. Box содержит два подобъекта, и один ссылается на другой. Это всегда плохая идея. Возможно, лучшим решением будет избавиться от Box::item_ и заставить Drawer содержать элемент по значению.

2. @n.’местоимения’m. Да, я не уверен, какой дизайн лучше всего выбрать для OP. Но лучше всего было бы полностью устранить проблему с помощью дизайна. Со временем это может стать немного хрупким для поддержания.

Ответ №2:

  Box() : drawer_(Drawer(item_)) {}
  

Вы создали Drawer(item_) объект, а затем вызвали конструктор копирования для drawer_() . Конструктор копирования по умолчанию не всегда обрабатывает сложные структуры данных.

Попробуй

 Box() : drawer_(item_) {}
  

для вызова обычного конструктора для Drawer

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

1. Спасибо. Я отредактировал свой вопрос. К сожалению, это не помогает.

2. Сгенерированный компилятором конструктор копирования для Drawer работает просто отлично.