我有一个这样的课程:
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>
答案 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::map
或boost::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;
};