C++ 如何使用子类创建 unordered_map

C++ How to make an unordered_map with subclasses

我正在制作游戏引擎,目前正在研究实体组件系统。这里有一些代码可以更好地理解:

class Entity {
    public:
        ...
        void AddComponent(EntityComponent component, unsigned int id){
        m_Components.insert({id, component});}

        EntityComponent GetComponent(unsigned int id)
        {
        auto i = m_Components.find(id);
        ASSERT(i != m_Components->end()); // ASSERT is a custom macro
        return i->second;
        }

    private:
        ...
        std::unordered_map<unsigned int, EntityComponent> m_Components;
    };

也是一个简单的parent class EntityComponent,只处理ID(我跳过了一些代码,因为它在这个问题中无关紧要):

class EntityComponent {
    public:
        ...

    private:

        unsigned int m_EntityID;
        size_t m_PoolIndex;
    };

它是 children。其中之一是 TransformComponent:

class TransformComponent : public EntityComponent {

    public:

        TransformComponent(unsigned int entityID, unsigned int poolID = 0, glm::vec2 position = glm::vec2(1.0,1.0), glm::vec2 scale = glm::vec2(1.0,1.0), float rotation = 0) : EntityComponent(entityID, poolID),m_Rotation(rotation), m_Scale(scale), m_Position(position){}

        inline glm::vec2 GetPosition() { return m_Position; }
        inline glm::vec2 GetScale() { return m_Scale; }

    private:

        glm::vec2 m_Position;
        float m_Rotation;
        glm::vec2 m_Scale;

    };

所以问题是我想创建一个实体,向它添加一个 TransformComponent 并使用它的函数 GetPosition()、GetScale()。

创建实体并添加 TransformComponent 有效,但是当我想使用 GetComponent(id) 时,它 returns parent class EntityComponent, 所以这意味着我不能使用像 GetPosition() 等函数

如何更改代码,以便我可以将 EntityComponent 的不同 children 添加到 unordered_map 并使用它们的 ID 接收它们并使用它们public 方法?

如前所述,第一个问题是你应该存储EntityComponent*或EntityComponent的智能指针,以防止派生对象数据在分配给基时被擦除class。因此,容器类型将是以下之一:

std::unordered_map<int, EntityComponent*>
std::unordered_map<int, std::unique_ptr<EntityComponent>>
std::unordered_map<int, std::shared_ptr<EntityComponent>> 

此外,应将虚拟析构函数添加到 EntityComponent 以正确删除派生的 classes:

class EntityComponent
{
    virtual ~EntityComponent() = default;
    // other stuff ...     
};

何时可以使用 dynamic_cast 检查 EntityComponent 是否实际上是一个 TransformComponent:

EntityComponent* ec = GetComponentById(id); // suppose that function returns TransformComponent or EntityComponent
TransformComponent* tc = dynamic_cast<TransformComponent*>(ec);
if (tc != nullptr)
{
    std::cout << "tc is transform component" << std::endl;
    tc->GetPosition();
    tc->GetScale();
}

但可能对于游戏引擎来说 dynamic_cast 在某些情况下可能会很慢。如果您知道从 EntityComponent 派生的所有 classes,您可以添加 Type 字段来检查实际对象类型,然后使用在编译时工作的 static_cast 转换为它:

enum class ComponentType
{
    BASE, 
    TRANSFORM,
    // other types
};

class EntityComponent
{
    ComponentType _type;
public:
    inline ComponentType GetType() const { return this->_type; }

    inline EntityComponent(ComponentType type = ComponentType::BASE) : _type(type) { }
    virtual ~EntityComponent() = default;
    // other staff
};

class TransformComponent : public EntityComponent
{
public:
    inline TransformComponent() : EntityComponent(ComponentType::TRANSFORM) { }
    // other staff
};

int main()
{
    EntityComponent* ec = GetComponentById(0);
    if (ec->GetType() == ComponentType::TRANSFORM)
    {
        std::cout << "ec is transform component" << std::endl;
        TransformComponent* tc = static_cast<TransformComponent*>(ec);
        tc->GetPosition();
        tc->GetScale();
    }
}

有几种方法可以将 TransformComponent 添加到映射中,如 std::unique_ptr<EntityComponent>:

std::unordered_map<int, std::unique_ptr<EntityComponent>> components;

// emplace raw pointer
int id = 0;
components.emplace(id, new TransformComponent());

// release unique_ptr of derived class and emplace
auto tc = std::make_unique<TransformComponent>();
components.emplace(id, tc.release());

// create unique_ptr of base class and move it to map
std::unique_ptr<EntityComponent> ec(new TransformComponent());
components.insert(id, std::move(ec));