【问题标题】:How to wrap a list of pointers to abstract class?如何包装指向抽象类的指针列表?
【发布时间】:2020-09-05 18:53:14
【问题描述】:

我尝试将指向抽象类 (list<shared_ptr<Base>> list_) 的智能指针列表包装到一些类 (ItemDrawerBox) 中。然后在主函数中,我有一个Box'es 的map,但它不起作用。我找到了一种方法,我可以使用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

【问题讨论】:

  • 地址消毒剂是你的朋友。它立即表明该程序正在做一些古怪的事情。

标签: c++ pointers polymorphism abstract-class smart-pointers


【解决方案1】:

在这行里

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 包含两个子对象,一个引用另一个。这总是一个坏主意。也许更好的解决方案是去掉 Box::item_ 并让 Drawer 按值包含一个项目。
  • @n.'pronouns'm。是的,我不确定 OP 的最佳设计选择是什么。但是通过设计完全消除问题将是最好的事情。随着时间的推移,这可能会变得有点脆弱。
【解决方案2】:
 Box() : drawer_(Drawer(item_)) {}

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

试试

Box() : drawer_(item_) {}

调用Drawer的普通构造函数

【讨论】:

  • 谢谢。我编辑了我的问题。不幸的是,它没有帮助。
  • Drawer 的编译器生成的复制构造函数工作正常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-01
  • 2020-03-27
  • 1970-01-01
  • 2015-12-28
  • 2010-12-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多