如何包装指向抽象类的指针的列表?

问题描述

我尝试将抽象类(list<shared_ptr<Base>> list_)的智能指针列表包装到某些类(ItemDrawerBox)中。然后在主函数中,我有一个map的{​​{1}},它不起作用。我找到了解决方法,可以使用Box,但是我怀疑它只会导致我看不到的错误。如何使其运作?这是代码

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>>& get_list() { return list_; }
private:
    list<shared_ptr<Base>> list_;
};

class Drawer {
public:
    Drawer(Item& item) : item_(item) {}
    void Draw() {
        list<shared_ptr<Base>>& 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:
    Item& 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 (auto& 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 (auto& b : pointers) {  b.second->Draw(); }
    for (auto& b : pointers) {  delete b.second; }
}

解决方法

在此行

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

您正在对中创建一个临时Box对象,该对象已移到地图中。

我们称它们为Box1(创建的临时对象)和Box2(地图中移动构建的对象)。

在创建Box1时,它正确地具有一个抽屉,该抽屉引用了Box1中的项目。

当我们将其移至地图中时,会得到Box2,它的抽屉仍指向Box1中的项目。

当我们继续前进时

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

Box1已被销毁,不再存在。因此,当我们尝试使用对它的引用时,我们使用的是UB的悬空引用。在这种情况下,您得到的结果为0,但同样可能会导致崩溃或任何随机输出。

要解决此问题,我们可以向Box添加一个副本构造函数来解决此问题。

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

现在副本的抽屉将指向正确的项目。

关于带有指针的版本为什么起作用的原因,因为我们正在复制指针,所以相同的对象将一直保留直到被删除。没有对象被移动或复制,只有指针被复制,并且复制的指针仍然指向正确的对象。

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

您已经创建了Drawer(item_)对象,然后为drawer_()调用了复制构造函数。默认的复制构造函数并不总是处理复杂的数据结构。

尝试

Box() : drawer_(item_) {}

调用Drawer的普通构造函数