如何在数组中存储不同的对象并获取某种类型的对象?

时间:2017-09-01 16:32:26

标签: c++ types enums casting

您可能听说过no guarantee of atomicity,其中所有内容都是Entity,每个实体都有一个控制其功能的Component列表。

我试图找出如何在数组中存储不同的对象(每个继承Component),并能够根据类型从该数组中获取对象。

我能想到的第一个解决方案是为继承组件的对象类型设置enum

enum ComponentType : unsigned char    // There will never be more than 256 components
{
    EXAMPLE_COMPONENT,
    ANOTHER_EXAMPLE_COMPONENT,
    AND_ANOTHER_EXAMPLE_COMPONENT
};

然后Component基类有一个带有getter的ComponentType type;,每个子组件都设置它的类型,例如:

ExampleComponent::ExampleComponent()
{
    type = EXAMPLE_COMPONENT;
}

然后我有一个GetComponent功能:

Component* Entity::GetComponent(ComponentType type)
{
    for (unsigned int i = 0; i < m_components.size(); i++)
    {
        if (m_components.at(i).GetType() == type)
        {
            return &m_components.at(i);
        }
    }

    return nullptr;
}

// Note: m_components is an std::vector;

最后你会打电话给GetComponent例如:

(ExampleComponent*) component = entity.GetComponent(EXAMPLE_COMPONENT);

问题在于,每种类型的组件都需要enum,并且在使用GetComponent之后还必须强制转换组件,以确保可以访问自己的成员变量。

有没有人知道在不需要enum的情况下执行此操作的正确方法,并且无需转换组件?如果有一个解决方案仍然需要在每个组件中存储一个类型变量,那么它最好是一个字节,并且不能超过4个字节。

编辑:我也不想使用模板

提前致谢!

大卫

1 个答案:

答案 0 :(得分:1)

您的方法模拟多态性:将type作为成员并检查该类型的if语句通常表示使用类层次结构。您已经声明要使用从Component类型派生的对象,因此您还应该正确使用多态性。

您的方法中的第二个问题是您要过滤“特定类型”,这或多或少等同于向下投射 - 即dynamic_cast<>():当您传递某个ComponentType时到Entity::GetComponent(),它返回一个指向Component的指针,但该指针后面的对象始终是特定派生类的对象:在你的例子中,你总是得到一个ExampleComponent对象,当你将EXAMPLE_COMPONENT传递给该函数。

自然会出现以下问题:您想对此函数返回的对象做什么?您只能从Component接口/类调用方法,但不能从派生类调用方法!因此,向下倾斜几乎没有任何意义(如果你将返回一个指向从Component派生的类的对象的指针,那么它将会发生。

以下是使用多态和在getComponent()方法中使用向下转换的方式,返回指向派生类的指针 - 请注意,该方法是一个模板,可以方便地为从{{派生的每个类实现此方法1}}:

Component

#include <string> #include <vector> #include <iostream> class Component { public: virtual std::string getType() = 0; }; using ComponentContainer = std::vector<Component*>; class AComponent : public Component { public: virtual std::string getType() { return "A"; }; }; class BComponent : public Component { public: virtual std::string getType() { return "B"; }; }; class CComponent : public Component { public: virtual std::string getType() { return "C"; }; }; class Entity { public: template <typename T> T* getComponent(); void putComponent(Component* c) { m_components.push_back(c); } private: ComponentContainer m_components; }; template<typename T> T* Entity::getComponent() { T* t = nullptr; for (auto i : m_components) { if ((t = dynamic_cast<T*>(i)) != nullptr) break; } return t; } int main() { Entity e; e.putComponent(new AComponent{}); e.putComponent(new BComponent{}); Component* c; if ((c = e.getComponent<AComponent>()) != nullptr) std::cout << c->getType() << std::endl; // delete all the stuff return 0; } 的大量使用从性能和设计角度来看都存在问题:它应该很少使用,如果有的话。

所以设计问题可能是一切都存储在一个容器中?您可以根据“行为”使用多个容器。由于行为在ECS中实现为派生类或接口,因此dynamic_cast<>() - 此实体的类似方法仅返回某些(子)接口的对象。然后,这些组件都将实现给定的接口方法,因此将消除向下铸造的需要。

例如,假设您有“可绘制组件”,则表明层次结构:

getComponent()

然后,一个实体可以有一个// Drawable interface class DrawableComponent : public Component { public: virtual void draw() const = 0; }; // Drawable objects derive from DrawableComponent class DComponent : public DrawableComponent { public: virtual void draw() const { /* draw the D component */ } }; 个对象的容器,你只需迭代这些对象并在每个对象上调用DrawableComponent

draw()