如何包装指向抽象 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_;
};
现在副本的抽屉将引用正确的项目。
至于为什么带有指针的版本可以工作,因为我们正在复制指针,所以相同的对象一直保留到它被删除。没有移动或复制对象,只有指针被复制,复制的指针仍然指向正确的对象。
我尝试将智能指针列表包装到抽象 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_;
};
现在副本的抽屉将引用正确的项目。
至于为什么带有指针的版本可以工作,因为我们正在复制指针,所以相同的对象一直保留到它被删除。没有移动或复制对象,只有指针被复制,复制的指针仍然指向正确的对象。