#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
работает просто отлично.