我可以按类型访问“组件”吗?

时间:2011-05-05 22:07:30

标签: c++ boost types mapping

我有一个这样的课程:

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

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

myEntity.get<Component1>();

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

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

但是我必须确保为每个组件使用不同的id,这对于mantainance是不利的。

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

我也希望O(1)能够访问一个组件,因为myEntity::get<T>经常被使用(不管是否有15-20个组件,无论如何都是有意义的),但这不是强制性的。 / p>

8 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:3)

可以使用基于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()功能通常会表明,您的班级设计确实很差。

答案 2 :(得分:3)

你可以这样做:

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内使用tie-tuple来返回正确的引用。维护成本很低(每次添加/删除组件时都会更改Entity::get)。这也留给了读者一个练习(不要忘记考虑新密钥的格式为Component&!)。

答案 3 :(得分:2)

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

答案 4 :(得分:1)

如果您将组件提供给每个人和他们的狗,为什么不简单地将它们公之于众?完成任务时没有复制和粘贴。

答案 5 :(得分:1)

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

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

看看使用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;
};