Izz*_*zzo 2 c++ inheritance smart-pointers unique-ptr
我目前正在用C++编写游戏.这个游戏有一个GameManager类.GameManager类包含一个包含指向游戏对象的指针的地图.我已经定义了一个GameObject类,它是一个简单地充当接口的抽象类.
我已经定义了两个派生自GameObject类的类:Enemy和Loot.
我希望我的GameManager类包含游戏对象的地图,或者更确切地说,指向游戏对象的指针.因为我的GameManager拥有这些对象,所以我希望地图包含std :: unique_ptr.
但是,我实际上很难将衍生对象(例如Enemy和Loot)添加到此地图中.
我希望我的GameManager迭代游戏对象并调用抽象方法.从本质上讲,我的GameManager并不关心某些东西是敌人,战利品还是其他东西,它只是想要能够调用基类中声明的"draw"方法.
我如何将指向派生类的unique_ptr添加到包含对基类的unique_ptr的映射?到目前为止,我的尝试导致代码无法编译.我一直收到一个错误,指出我不允许动态地将派生类指针强制转换为基类指针.
如果我使用原始指针,我觉得这个工作正常,但我打算使用智能指针.
码:
#include <memory>
#include <map>
class GameObject
{
public:
virtual void draw() = 0;
};
class Enemy : GameObject
{
public:
void draw() {};
};
class Loot : GameObject
{
public:
void draw() {};
};
int main()
{
std::map<int, std::unique_ptr<GameObject>> my_map;
// How do I add an Enemy or Loot object unique_ptr to this map?
my_map[0] = dynamic_cast<GameObject>(std::unique_ptr<Enemy>().get()); // doesn't compile, complains about being unable to cast to abstract class
return 0;
}
Run Code Online (Sandbox Code Playgroud)
错误消息的第一个原因是类型永远不能用作a的类型dynamic_cast.目标类型dynamic_cast必须始终是指向类类型的指针(意味着如果转换失败,结果为null)或类类型的引用(意味着如果转换失败则抛出异常).
所以改进#1:
my_map[0] = dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get());
Run Code Online (Sandbox Code Playgroud)
但这不起作用,因为GameObject是私人基类Enemy.你可能想要使用公共继承,但是(当使用class而不是struct)时你必须这样说:
class Enemy : public GameObject
// ...
Run Code Online (Sandbox Code Playgroud)
接下来我们会发现=map语句中的内容无效.左侧有类型std::unique_ptr<GameObject>,没有任何operator=可以GameObject*指针的类型.但它确实有一个reset用于设置原始指针的成员:
my_map[0].reset(dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get()));
Run Code Online (Sandbox Code Playgroud)
现在声明应该编译 - 但它仍然是错误的.
在得出错误的原因之前,我们可以进行简化.dynamic_cast需要从指向基类的指针获取派生类的指针,或者在更复杂的继承树中获取许多其他类型更改.但是根本不需要从指向派生类的指针获取指向基类的指针:这是一个有效的隐式转换,因为每个具有派生类类型的对象必须始终包含基类类型的子对象,并且没有"失败"案例.所以dynamic_cast这里可以放弃.
my_map[0].reset(std::unique_ptr<Enemy>().get());
Run Code Online (Sandbox Code Playgroud)
下一个问题是std::unique_ptr<Enemy>()创建一个null unique_ptr,根本没有Enemy创建任何对象.要创建一个实际的Enemy,我们可以改为编写std::unique_ptr<Enemy>(new Enemy)或者std::make_unique<Enemy>().
my_map[0].reset(std::make_unique<Enemy>().get());
Run Code Online (Sandbox Code Playgroud)
仍然是错误的,并且有点棘手.现在问题是创建的Enemy对象由std::unique_ptr<Enemy>返回的临时对象拥有make_unique.该reset告诉std::unique_ptr<GameObject>它应该拥有一个指向同一个对象可在地图内.但是在声明的最后,临时std::unique_ptr<Enemy>被破坏,它会破坏Enemy对象.所以地图留下了一个指向死对象的指针,这个指针无效 - 不是你想要的.
但这里的解决方案是,我们不需要更动get(),并reset()在所有.有一个operator=允许std::unique_ptr<Enemy>为a 分配rvalue std::unique_ptr<GameObject>,它在这里做正确的事情.它利用隐式转换从Enemy*到GameObject*.
my_map[0] = std::make_unique<Enemy>();
Run Code Online (Sandbox Code Playgroud)
(注意,如果你有一个名字 std::unique_ptr<Enemy>,你需要std::move它来允许分配,如同my_map[0] = std::move(enemy_ptr);.但std::move上面不需要,因为结果make_unique已经是一个右值.)
现在这个陈述更短,更清晰,实际上会做你想要的.
评论也提出了可能性
my_map.emplace(0, std::make_unique<Enemy>());
Run Code Online (Sandbox Code Playgroud)
这也是有效的,但是可能存在一个重要的区别:如果地图已经有一个键为零的对象,那么=版本将销毁并替换旧的emplace版本,但版本将单独保留地图,而刚刚创建的Enemy将被销毁.
| 归档时间: |
|
| 查看次数: |
162 次 |
| 最近记录: |