如何将抽象基础 class unique_ptrs 添加到地图?
How do you add abstract base class unique_ptrs to a map?
我目前正在用 C++ 编写游戏。这个游戏有一个 GameManager class。 GameManager class 包含一个地图,其中包含指向游戏对象的指针。我定义了一个 GameObject class,它是一个抽象的 class,仅作为一个接口。
我定义了两个 class派生自 GameObject class 的元素:Enemy 和 Loot。
我希望我的 GameManager class 包含游戏对象的映射,或者更确切地说,是指向游戏对象的指针。因为我的 GameManager 拥有这些对象,所以我希望地图包含 std::unique_ptr 个。
但是,我在实际向这张地图添加衍生对象(例如敌人和战利品)时遇到了困难。
我希望我的 GameManager 遍历游戏对象并调用抽象方法。本质上,我的 GameManager 不关心某物是否是敌人、战利品或其他任何东西,它只希望能够调用在基础 class.[=13= 中声明的 "draw" 方法]
我如何将指向派生 class 的 unique_ptr 添加到包含 unique_ptr 到基础 class 的地图?到目前为止,我的尝试导致了我无法编译的代码。我不断收到一条错误消息,指出不允许将派生 class 指针动态转换为基 class 指针。
如果我使用原始指针,我觉得这个工作很好,但我打算使用智能指针。
代码:
#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;
}
dynamic_cast
只能用于指针和引用之间的转换。 GameObject
既不是指针类型,也不是引用类型,所以你不能 dynamic_cast
它。
您可能原本打算 dynamic_cast<GameObject*>
。然而,你不应该 dynamic_cast
指向一个(指向)基数 class。指向派生类型的指针可以隐式转换为基本 class 指针。当不需要隐式转换时使用 static_cast
。此外,这种转换也是不可能的,因为强制转换在任何成员函数之外,因此无法访问私有基 class.
此外,您不能将裸指针分配给唯一指针。要将裸指针的所有权转移到唯一指针,可以使用 unique_ptr::reset
。但是,您永远不应该将 unique_ptr::get
中的指针存储到另一个唯一指针中。当两个唯一指针析构函数都试图销毁同一个对象时,这样做将导致未定义的行为。幸运的是,在这种情况下,指针是值初始化的,因此为 null,因此从技术上讲,这个错误没有任何后果。但是你是故意使用空指针的吗?我怀疑不是。
将指向派生对象的唯一指针插入到指向基的唯一指针的映射中很简单。让 ptr
成为指向 Enemy
的唯一指针:
std::unique_ptr<Enemy> ptr = get_unique_pointer_from_somewhere();
只需移动分配唯一指针:
my_map[0] = std::move(ptr);
或者,您可以使用映射的 emplace
成员函数。
最后,如果析构函数 unique_ptr<GameObject>
指向派生对象,则它会有未定义的行为。要修复,请声明 GameObject
virtual.
的析构函数
错误消息的第一个原因是 class 类型永远不能用作 dynamic_cast
的类型。 dynamic_cast
的目标类型必须始终是指向 class 类型的指针(意味着如果转换失败则结果为 null)或对 class 类型的引用(意味着抛出异常如果施法失败)。
所以改进 #1:
my_map[0] = dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get());
但这行不通,因为 GameObject
是 Enemy
的私人基地 class。您可能打算使用 public 继承,但是(当使用 class
而不是 struct
时)您必须这样说:
class Enemy : public GameObject
// ...
接下来我们会发现map语句中的=
是无效的。左侧有类型 std::unique_ptr<GameObject>
,它没有任何可以接受 GameObject*
指针的 operator=
。但它确实有一个 reset
成员用于设置原始指针:
my_map[0].reset(dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get()));
现在该语句应该可以编译 - 但它仍然是错误的。
在弄清错误原因之前,我们可以做一个简化。 dynamic_cast
用于从指向基 class 的指针获取指向派生 class 的指针,或者用于更复杂的继承树中的许多其他类型更改。但是根本不需要从指向派生 class 的指针获取指向基 class 的指针:这是一个有效的隐式转换,因为每个具有派生 class 类型的对象都必须始终包含基本 class 类型的子对象,没有 "failure" 的情况。所以这里的dynamic_cast
可以去掉。
my_map[0].reset(std::unique_ptr<Enemy>().get());
下一个问题是 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());
还是错了,而且有点棘手。现在的问题是创建的 Enemy
对象属于 make_unique
返回的临时 std::unique_ptr<Enemy>
对象。 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);
. 但是上面不需要 std::move
因为 make_unique
的结果已经是一个右值。)
现在这个语句更短、更清晰,而且实际上会做你想做的。
一条评论也暗示了这种可能性
my_map.emplace(0, std::make_unique<Enemy>());
这也是有效的,但可能有一个重要的区别:如果地图已经有一个键为零的对象,=
版本将销毁并替换旧的,但 emplace
版本将单独保留地图,而刚刚创建的 Enemy
将被销毁。
我目前正在用 C++ 编写游戏。这个游戏有一个 GameManager class。 GameManager class 包含一个地图,其中包含指向游戏对象的指针。我定义了一个 GameObject class,它是一个抽象的 class,仅作为一个接口。
我定义了两个 class派生自 GameObject class 的元素:Enemy 和 Loot。
我希望我的 GameManager class 包含游戏对象的映射,或者更确切地说,是指向游戏对象的指针。因为我的 GameManager 拥有这些对象,所以我希望地图包含 std::unique_ptr 个。
但是,我在实际向这张地图添加衍生对象(例如敌人和战利品)时遇到了困难。
我希望我的 GameManager 遍历游戏对象并调用抽象方法。本质上,我的 GameManager 不关心某物是否是敌人、战利品或其他任何东西,它只希望能够调用在基础 class.[=13= 中声明的 "draw" 方法]
我如何将指向派生 class 的 unique_ptr 添加到包含 unique_ptr 到基础 class 的地图?到目前为止,我的尝试导致了我无法编译的代码。我不断收到一条错误消息,指出不允许将派生 class 指针动态转换为基 class 指针。
如果我使用原始指针,我觉得这个工作很好,但我打算使用智能指针。
代码:
#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;
}
dynamic_cast
只能用于指针和引用之间的转换。 GameObject
既不是指针类型,也不是引用类型,所以你不能 dynamic_cast
它。
您可能原本打算 dynamic_cast<GameObject*>
。然而,你不应该 dynamic_cast
指向一个(指向)基数 class。指向派生类型的指针可以隐式转换为基本 class 指针。当不需要隐式转换时使用 static_cast
。此外,这种转换也是不可能的,因为强制转换在任何成员函数之外,因此无法访问私有基 class.
此外,您不能将裸指针分配给唯一指针。要将裸指针的所有权转移到唯一指针,可以使用 unique_ptr::reset
。但是,您永远不应该将 unique_ptr::get
中的指针存储到另一个唯一指针中。当两个唯一指针析构函数都试图销毁同一个对象时,这样做将导致未定义的行为。幸运的是,在这种情况下,指针是值初始化的,因此为 null,因此从技术上讲,这个错误没有任何后果。但是你是故意使用空指针的吗?我怀疑不是。
将指向派生对象的唯一指针插入到指向基的唯一指针的映射中很简单。让 ptr
成为指向 Enemy
的唯一指针:
std::unique_ptr<Enemy> ptr = get_unique_pointer_from_somewhere();
只需移动分配唯一指针:
my_map[0] = std::move(ptr);
或者,您可以使用映射的 emplace
成员函数。
最后,如果析构函数 unique_ptr<GameObject>
指向派生对象,则它会有未定义的行为。要修复,请声明 GameObject
virtual.
错误消息的第一个原因是 class 类型永远不能用作 dynamic_cast
的类型。 dynamic_cast
的目标类型必须始终是指向 class 类型的指针(意味着如果转换失败则结果为 null)或对 class 类型的引用(意味着抛出异常如果施法失败)。
所以改进 #1:
my_map[0] = dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get());
但这行不通,因为 GameObject
是 Enemy
的私人基地 class。您可能打算使用 public 继承,但是(当使用 class
而不是 struct
时)您必须这样说:
class Enemy : public GameObject
// ...
接下来我们会发现map语句中的=
是无效的。左侧有类型 std::unique_ptr<GameObject>
,它没有任何可以接受 GameObject*
指针的 operator=
。但它确实有一个 reset
成员用于设置原始指针:
my_map[0].reset(dynamic_cast<GameObject*>(std::unique_ptr<Enemy>().get()));
现在该语句应该可以编译 - 但它仍然是错误的。
在弄清错误原因之前,我们可以做一个简化。 dynamic_cast
用于从指向基 class 的指针获取指向派生 class 的指针,或者用于更复杂的继承树中的许多其他类型更改。但是根本不需要从指向派生 class 的指针获取指向基 class 的指针:这是一个有效的隐式转换,因为每个具有派生 class 类型的对象都必须始终包含基本 class 类型的子对象,没有 "failure" 的情况。所以这里的dynamic_cast
可以去掉。
my_map[0].reset(std::unique_ptr<Enemy>().get());
下一个问题是 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());
还是错了,而且有点棘手。现在的问题是创建的 Enemy
对象属于 make_unique
返回的临时 std::unique_ptr<Enemy>
对象。 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);
. 但是上面不需要 std::move
因为 make_unique
的结果已经是一个右值。)
现在这个语句更短、更清晰,而且实际上会做你想做的。
一条评论也暗示了这种可能性
my_map.emplace(0, std::make_unique<Enemy>());
这也是有效的,但可能有一个重要的区别:如果地图已经有一个键为零的对象,=
版本将销毁并替换旧的,但 emplace
版本将单独保留地图,而刚刚创建的 Enemy
将被销毁。