代码之家  ›  专栏  ›  技术社区  ›  Izzo

如何将抽象基类唯一指针添加到映射中?

  •  2
  • Izzo  · 技术社区  · 7 年前

    我现在正在用C++编程一个游戏。这个游戏有一个GameManager类。GameManager类包含一个地图,其中包含指向游戏对象的指针。我定义了一个gameobject类,它是一个抽象类,只是作为一个接口。

    我定义了两个从游戏对象类派生的类:敌人和抢劫。

    我希望GameManager类包含游戏对象的映射,或者更确切地说,指向游戏对象的指针。因为我的GAMEMANER拥有这些对象,所以我希望地图包含STD::UnQuyJPTR。

    但是,我很难在地图上添加衍生对象(例如敌人和战利品)。

    我希望我的GameManager迭代游戏对象并调用抽象方法。基本上,我的游戏管理员不关心什么东西是敌人、战利品或其他什么东西,它只是想能够调用基类中声明的“draw”方法。

    如何将指向派生类的唯一指针添加到包含指向基类的唯一指针的映射中?到目前为止,我的尝试导致了我无法编译的代码。我不断地得到一个错误,声明不允许我动态地将派生类指针强制转换为基类指针。

    如果我使用的是原始指针,我觉得这样做很好,但是我打算使用智能指针。

    代码:

    #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;
    }
    
    2 回复  |  直到 7 年前
        1
  •  5
  •   aschepler    7 年前

    错误消息的第一个原因是类类型不能用作 dynamic_cast .目标类型 动态铸造 必须始终是指向类类型的指针(表示如果强制转换失败,结果为空)或对类类型的引用(表示如果强制转换失败,则引发异常)。

    所以改进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()));
    

    现在语句应该编译了——但它仍然是错误的。

    在找出错误的原因之前,我们可以简化一下。 动态铸造 需要从指向基类的指针获取指向派生类的指针,或者在更复杂的继承树中进行许多其他类型更改。但根本不需要从指向派生类的指针获取指向基类的指针:这是一个有效的隐式转换,因为具有派生类类型的每个对象都必须始终包含基类类型的子对象,并且不存在“失败”情况。所以 动态铸造 这里可以放下。

    my_map[0].reset(std::unique_ptr<Enemy>().get());
    

    下一个问题是 std::unique_ptr<Enemy>() 创建空值 unique_ptr 和否 敌人 对象是完全创建的。创建实际 敌人 我们也可以写 std::unique_ptr<Enemy>(new Enemy) std::make_unique<Enemy>() .

    my_map[0].reset(std::make_unique<Enemy>().get());
    

    仍然是错误的,而且有点棘手。现在的问题是 敌人 对象属于临时 std::unique_ptr<Enemy> 对象返回者 make_unique .这个 重置 告诉 STD::UngQuyPPTR & lt;游戏对象& GT; 在映射中,它应该拥有指向同一对象的指针。但在声明的结尾,临时 STD::UngQuyPPTR & L.; 被摧毁,它摧毁了 敌人 对象。所以地图上留下了一个指向一个死对象的指针,这是无效的-不是你想要的。

    但这里的解决办法是我们不需要到处乱搞 get() reset() 完全。有一个 操作员= 允许分配一个右值 STD::UngQuyPPTR & L.; STD::UngQuyPPTR & lt;游戏对象& GT; 它在这里做了正确的事情。它使用隐式转换 Enemy* 游戏对象* .

    my_map[0] = std::make_unique<Enemy>();
    

    (注意,如果您有 已命名 STD::UngQuyPPTR & L.; ,你需要 std::move 允许转让,如 my_map[0] = std::move(enemy_ptr); .但是 STD::移动 上面不需要,因为 使…独一无二 已经是一个右值。)

    现在这句话要简短得多,也要清晰得多,而且会真正做到你想要的。

    一条评论也暗示了这种可能性

    my_map.emplace(0, std::make_unique<Enemy>());
    

    这也是有效的,但可能有一个重要的区别:如果映射已经有一个键为零的对象,则 = 版本将销毁并替换旧版本,但 emplace 版本将只保留地图和刚刚创建的 敌人 将被销毁。

        2
  •  0
  •   eerorika    7 年前

    dynamic_cast 只能用于在指针和引用之间转换。 GameObject 既不是指针类型,也不是引用类型,因此不能 动态铸造 为了它。

    你可能是故意的 dynamic_cast<GameObject*> 相反。但是,你不应该 动态铸造 指向(指向)基类的指针。指向派生类型的指针可隐式转换为基类指针。使用 static_cast 当不需要隐式转换时。此外,这种转换也是不可能的,因为强制转换在任何成员函数之外,因此不能访问私有基类。

    此外,不能将裸指针分配给唯一指针。要将裸指针的所有权转移到唯一指针,可以使用 unique_ptr::reset .但是,您不应该存储来自 unique_ptr::get 到另一个唯一的指针。当两个唯一指针析构函数都试图销毁同一对象时,这样做将导致未定义的行为。幸运的是,在这种情况下,指针是值初始化的,因此是空的,所以从技术上讲,这个错误没有任何后果。但您是否有意使用空指针?我怀疑不是。

    将指向派生对象的唯一指针插入到指向基的唯一指针的映射中很简单。让 ptr 成为唯一指向 Enemy 以下内容:

    std::unique_ptr<Enemy> ptr = get_unique_pointer_from_somewhere();
    

    只需移动指定唯一指针:

    my_map[0] = std::move(ptr);
    

    或者,你可以用 emplace 映射的成员函数。

    最后,析构函数 unique_ptr<GameObject> 如果指向派生对象,将具有未定义的行为。要修复,请声明的析构函数 游戏对象 事实上的。