我目前正在用C ++编写游戏。该游戏设有GameManager类。 GameManager类包含一个地图,其中包含指向游戏对象的指针。我定义了一个GameObject类,它是一个仅充当接口的抽象类。
我定义了两个从GameObject类派生的类:Enemy和Loot。
我希望我的GameManager类包含游戏对象的地图,或更确切地说,指向游戏对象的指针。因为我的GameManager拥有这些对象,所以我希望地图包含std :: unique_ptr。
但是,我很难在此地图上实际添加派生对象(例如,敌人和战利品)。
我希望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;
}
答案 0 :(得分:5)
产生错误消息的第一个原因是,类类型永远不能用作dynamic_cast
的类型。 dynamic_cast
的目标类型必须始终是指向类类型的指针(如果强制转换失败,则结果为null)或对类类型的引用(意味着如果强制转换失败,则引发异常)。
所以改进#1:
my_map[0] = dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get());
但是这不起作用,因为GameObject
是Enemy
的私有基类。您可能打算使用公共继承,但是(当使用class
而不是struct
时),您必须这样说:
class Enemy : public GameObject
// ...
接下来,我们将发现map语句中的=
无效。左侧的类型为std::unique_ptr<GameObject>
,没有任何可以使用operator=
指针的GameObject*
。但是它确实有一个reset
成员用于设置原始指针:
my_map[0].reset(dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get()));
现在该语句应该可以编译了-但这仍然是错误的。
在弄清错误原因之前,我们可以进行简化。 dynamic_cast
对于从基础类的指针获取派生类的指针,或在更复杂的继承树中进行许多其他类型的更改都是必需的。但是完全不需要从指向派生类的指针中获取指向基类的指针:这是有效的隐式转换,因为每个具有派生类类型的对象都必须始终包含基类类型的子对象,并且没有“失败”的情况。因此,dynamic_cast
可以放在这里。
my_map[0].reset(std::unique_ptr<Enemy>().get());
下一个问题是std::unique_ptr<Enemy>()
创建一个空unique_ptr
,并且根本没有创建Enemy
对象。要创建实际的Enemy
,我们可以改写std::unique_ptr<Enemy>(new Enemy)
或std::make_unique<Enemy>()
。
my_map[0].reset(std::make_unique<Enemy>().get());
还是错了,而且有点棘手。现在的问题是,创建的Enemy
对象归std::unique_ptr<Enemy>
返回的临时make_unique
对象所有。 reset
告诉映射中的std::unique_ptr<GameObject>
应该拥有一个指向同一对象的指针。但是在该语句的末尾,临时std::unique_ptr<Enemy>
被销毁,并且销毁了Enemy
对象。因此,地图上留有指向死对象的指针,该对象无法使用-不是您想要的。
但是这里的解决方案是我们完全不需要弄乱get()
和reset()
。有一个operator=
可以为std::unique_ptr<Enemy>
分配右值std::unique_ptr<GameObject>
,并且在这里做正确的事情。它利用了从Enemy*
到GameObject*
的隐式转换。
my_map[0] = std::make_unique<Enemy>();
(请注意,如果您有一个名为 的std::unique_ptr<Enemy>
,则需要std::move
才能进行分配,就像my_map[0] = std::move(enemy_ptr);
一样。但是{{1上面不需要}},因为std::move
的结果已经是右值。)
现在,此语句更短,更清晰,并且将实际执行您想要的操作。
评论还暗示了可能性
make_unique
这也是有效的,但可能存在重要的区别:如果地图已经有一个键为零的对象,则my_map.emplace(0, std::make_unique<Enemy>());
版本将销毁并替换旧的键,而=
版本将销毁旧的键离开地图,刚创建的emplace
将被销毁。
答案 1 :(得分:0)
dynamic_cast
仅可用于在指针和引用之间进行转换。 GameObject
既不是指针类型,也不是引用类型,因此无法dynamic_cast
对其进行访问。
您可能打算使用dynamic_cast<GameObject*>
。但是,您不应该dynamic_cast
指向基类(指向它)。指向派生类型的指针可以隐式转换为基类指针。当不需要隐式转换时,请使用static_cast
。此外,该转换也不可能,因为强制转换在任何成员函数的外部,因此无法访问私有基类。
此外,您不能将裸指针分配给唯一指针。要将裸指针的所有权转移到唯一指针,可以使用unique_ptr::reset
。但是,永远不要将unique_ptr::get
的指针存储到另一个唯一的指针中。当两个唯一指针析构函数都尝试破坏同一对象时,这样做将导致未定义的行为。幸运的是,在这种情况下,指针是值初始化的,因此为null,因此从技术上讲,此错误不会有后果。但是,您是否故意使用了空指针?我怀疑不是。
将派生对象的唯一指针插入到base的唯一指针的映射中很简单。假设ptr
是指向Enemy
的唯一指针:
std::unique_ptr<Enemy> ptr = get_unique_pointer_from_somewhere();
只需移动即可分配唯一的指针:
my_map[0] = std::move(ptr);
或者,您可以使用地图的emplace
成员函数。
最后,析构函数unique_ptr<GameObject>
指向派生对象时将具有未定义的行为。要解决此问题,请声明GameObject
虚拟的析构函数。