我可以按类型访问"component"吗?

Can I gain access to a "component" by type?

本文关键字:component 访问 类型 我可以      更新时间:2023-10-16

我有这样的类:

class Component1 {...};
class Component2 {...};
class Component3 {...};
class Entity
{
  Component1 c1;
  Component2 c2;
  Component3 c3;
public:
  Component1& get_c1() { return c1;}
  Component2& get_c2() { return c2;}
  Component3& get_c3() { return c3;}
};

基本上,实体是所有可能类型的组件(以及其他组件)的容器。我的问题是我有超过 15 个不同的组件,我不喜欢以这种方式复制和粘贴行。我正在寻找类似的东西:

myEntity.get<Component1>();

以获得我需要的组件。我看了一下 boost::tuple,它很酷,但它允许使用整数作为键进行访问。我可以在每个 Component* 类中使用一个公共静态常量整数并获得如下访问权限:

myEntity.get<Component1::id>();

但是我必须确保为每个组件使用不同的 ID,这对维护不利。

有没有办法使用魔术(即模板)将类型"映射"到该类型的值,以便 myEntity.get<Component1>()按预期工作?

我还希望 O(1) 访问组件,因为 myEntity::get<T> 经常使用(并不是说 15-20 个组件无论如何谈论复杂性是有意义的),但这不是强制性的。

考虑使用 boost::fusion::map,这允许您将类型映射到值,例如:

typedef fusion::map<pair<Component1, Component1>, pair<Component2, Component2> etc.> map_t;
map_t m(make_pair<Component1>(Component()), make_pair<Component2>(Component2()));
// to access
at_key<Component1>(m); // reference to instance of Component1

我认为以上是正确的,抱歉简洁,在iPhone上不容易!

编辑:实际上,正如下面@Eugine所指出的,boost::fusion::set是一个更好的匹配,与上面类似的事情:

typedef fusion::set<Component1, Component2, etc.> set_t;
set_t s(Component1(), Component2());
// to access
at_key<Component1>(s); // reference to instance of Component1
可以使用

基于 CRTP 的解决方案。

template<typename Component> struct comp_internal {
    template<typename T> T& GetComponent();
};
template<typename Component> struct comp : public comp_internal {
    Component component;
public:
    Component& GetComponent<Component>() {
        return component;
    }
};
class Entity : public comp<Component1>, public comp<Component2> {
};

请注意,我实际上并没有尝试过这个,但我认为它应该有效。但是,像这样的垃圾邮件get()函数通常表明,嗯,你的类设计真的很差。

你可以

这样做:

class Entity {
public:
    template<typename Component>
    Component&
    get();
private:
    // convenience typedef since you mention 15+ components
    typedef boost::tuple<Component1, Component2, Component3> tuple_type;
    tuple_type tuple; // store components in a tuple
    template<typename Tuple, typename Key>
    struct lookup;
};
template<typename Tuple, typename Key>
struct Entity::lookup {
    /*
     * is_same is from the Boost TypeTraits library
     */
    static const int value =
        boost::is_same<typename Tuple::head_type, Key>::value ?
            0 :
            1 + lookup<typename Tuple::tail_type, Key>::value;
};
/*
 * still need an explicit specialization to end the recursion because the above
 * will eagerly instantiate lookup<boost::tuples::null_type, Key> even when
 * the key is found
 */
template<typename Key>
struct Entity::lookup<boost::tuples::null_type, Key> {
    static const int value = 0;
};
template<typename Component>
Component&
Entitiy::get()
{
    return boost::get<lookup<tuple_type, Component>::value>(tuple);
}

这执行线性查找,但仅在编译时(实际上在模板实例化方面)是 O(n);它在运行时是 O(1),所以也许你可以接受。 请注意,某些编译器具有 O(n) 模板查找,因此最终可能会进入 O(n^2) 编译时;我相信 C++11 将要求编译器进行常时模板查找。你也可以通过不急切地实例化递归来避免一些实例化,例如使用 Boost.MPL。为了简洁明了,我避免了这种情况。

以上依赖于 Boost Tuple 的高级功能,这些功能不适用于 std::tuple (C++11)。但是,我相信使用可变参数模板在 C++11 中实现lookup不会太难(作为练习留给读者;)。您也可以避免在不使用Boost.MPL的情况下急切的实例化。

其他备注:

  • 这要求每个组件具有不同的类型。
  • 在成员函数中,您将无法访问每个组件,因为您无法直接命名它们,而必须求助于调用get。我想您仍然可以将它们用作单个成员,并在Entity::get中使用连接元组来返回正确的引用。这将以很小的维护成本(每次添加/删除组件时更改Entity::get)。这也留给了读者一个练习(不要忘记考虑到新键的形式将是Component&

我几乎问了同样的问题:对我来说,boost::fusion::mapboost::fusion::set是矫枉过正的,我真的不喜欢超长的模板参数列表,如果我的容器中有超过 10 个,则必须设置一个宏。 我选择了这样的东西:

template <class T>
struct Holder
{
    T t;
};
struct A {};
struct B {};
struct Aggregate
    :
    Holder<A>, 
    Holder<B>  // add as many more as you need here
{
    template <class T>
    T &get()
    {
        return this->Holder<T>::t;
    }
};
Aggregate a;
a.get<A>();
a.get<B>();

如果你让你的组件对每个人和他们的狗开放,为什么不简单地将它们公开呢?无需复制粘贴即可完成任务。

在许多情况下,这有缺点,但在您的情况下,可能是"选择性衰减"就足够了:

class Entity
{
  Component1 c1;
  Component2 c2;
  Component3 c3;
public:
  operator Component1*() { return &c1;}
  operator Component2*() { return &c2;}
  operator Component3*() { return &c3;}
  template<class X> operator X*() { return 0; }
};

现在,您可以将 * 用作"组件选择器"作为

Entity* pe = ... //whatever gets you access to an Entity;
Component1* p1 = *pe; //will use operator Component1*()
Component4* p4 = *pe; //will use operator X*()
if(p1) { /* component1 exist */ }
if(p4) { /* component 4 exist */ }

我可能是错的,但在我看来,你正在寻找的是 Boost.Variant

但是,如果您希望所有组件都可用并且每个组件都有一个实例,那么变体不适合您。

查看使用 typeindex 和 typeid。您可以按模板类型将组件添加到地图中,其类型 id 是映射键。然后,您可以按类型从地图中获取组件。

#include <unordered_map>
#include <memory>
#include <typeindex>
#include "component.h"
class GameObject
{
public:
virtual ~GameObject(){}
template < typename T >
std::shared_ptr< T > GetComponent( void )
{
    auto it = m_component.find( typeid( T ) );
    if( it != m_component.end() )
        return std::dynamic_pointer_cast< T >( it->second );
    return nullptr;
}
protected:
template< typename T >
void AddComponent( void )
{
    static_assert( std::is_base_of< Component, T >::value, "Non-component class cannot be added!" );
    m_component[ typeid( T ) ] = std::static_pointer_cast< Component >( std::make_shared< T >() );
}
private:
std::unordered_map< std::type_index, std::shared_ptr< Component >>  m_component;
};