【问题标题】:Can I gain access to a "component" by type?我可以按类型访问“组件”吗?
【发布时间】:2011-08-19 18:24:36
【问题描述】:

我有这样的课:

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&lt;Component1&gt;() 按预期工作?

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

【问题讨论】:

  • 也许如果您描述的是您的实际问题,而不是您想象的解决方案,我们可以提供更多帮助。

标签: c++ boost types mapping


【解决方案1】:

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

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

【讨论】:

  • 不,因为他想静态保存所有类型,而不是在运行时改变包含的类型。
【解决方案2】:

也许可以使用基于 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() 函数通常表明,你的类设计确实有点糟糕。

【讨论】:

  • 我明白了。在我正在编程的游戏中,实体是一个游戏对象,组件是:动画、位置、IA 等。这种设计允许我在需要时访问我需要的组件。例如绘图部分只需要动画和位置。也许最好为每个组件维护一个不同的列表,但是很难找到类似的内容:“给我绑定到我现在正在处理的动画的位置。”实体是一个容器,有点更多我没有在这里报告的东西。
【解决方案3】:

如果您让每个人和他们的狗都可以使用您的组件,为什么不直接将它们公开?无需复制粘贴即可完成任务。

【讨论】:

  • @Downvoters:请告诉我原因,以便我改进这个答案。 :)
【解决方案4】:

考虑使用 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

【讨论】:

  • 为什么不是boost::fusion::set
  • 这就是我要找的。在运行时是否有 at_key O(1) 访问权限?
  • @happy_emi,不确定,通常 mpl 容器上的运行时函数是使用递归(ish)函数调用实现的。但是,一个好的优化器应该能够优化掉大部分调用,它可能是这样的。
  • @Eugen Constantin Dinca,是的,实际上设置更好,我在 iPhone 上睡觉,所以并没有真正考虑清楚。
【解决方案5】:

你可以这样做:

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&amp;!)。

【讨论】:

  • 这是我最初的想法,但在考虑了 OP 试图完成的工作后,我改变了主意,倾向于清晰。 +1实际上写出了所有这些。 :)
【解决方案6】:

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

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 */ }

【讨论】:

    【解决方案7】:

    我几乎问了同样的问题:对我来说,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>();
    

    【讨论】:

      【解决方案8】:

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

      #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;
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-03-21
        • 2016-01-14
        • 1970-01-01
        • 2016-11-07
        • 2021-12-26
        • 1970-01-01
        相关资源
        最近更新 更多