我可以按类型访问"component"吗?
Can I gain access to a "component" by type?
我有这样的类:
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::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>();
如果你让你的组件对每个人和他们的狗开放,为什么不简单地将它们公开呢?无需复制粘贴即可完成任务。
在许多情况下,这有缺点,但在您的情况下,可能是"选择性衰减"就足够了:
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;
};
- 通过方法访问结构
- 使用不带参数的函数访问结构元素
- 如果我只是不访问queue_front节点的子节点,而是将它们推到队列中呢?还是BFS吗
- 用于访问容器<T>数据成员的正确 API
- 访问者访问变体并返回不同类型时出错
- 尝试通过多个向量访问变量时,向量下标超出范围
- 无法访问嵌套类.类的使用无效
- 写入位置0x0000000C时发生访问冲突
- 我们可以访问一个不存在的联盟的成员吗
- C++从另一个类访问公共静态向量的正确方法是什么
- 我的简单if-else语句是如何无法访问的代码
- 从C++dll访问C#中的一行主要参数
- 概念TS检查忽略私有访问修饰符
- 访问被拒绝后,c++中的故障保护代码
- 在c++中访问int到类对象的映射时出错
- 我想访问std::unique_ptr中的一个特定元素
- 为什么示例代码访问IUnknown中已删除的内存
- C++:无法访问声明的受保护成员
- 通过指向指针数组的指针访问子类的属性
- 我可以按类型访问"component"吗?