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

How to wrap a list of pointers to abstract class?

我尝试将智能指针列表包装到抽象 class (list<shared_ptr<Base>> list_) 到一些 classes (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>>& 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; }
}

结果如下:

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
 Box() : drawer_(Drawer(item_)) {}

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

尝试

Box() : drawer_(item_) {}

调用 Drawer

的普通构造函数

这里这一行

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

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

我们称它们为 Box1,即创建的临时对象,以及 Box2,即映射中的 move-constructed 对象。

当创建 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_;
};

现在副本的抽屉将引用正确的项目。

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