可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a class like this:
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;}
};
Basically the Entity is a container of all possible types of components (with other stuff, too). My problem is that I have more than 15 different components and I don't like to copy&paste lines this way.
I'm looking for something like:
myEntity.get<Component1>();
to obtain the component I need. I took a look at boost::tuple which is cool but it allows access using an integer as key. I could use a public static const integer in each Component* class and gain access like this:
myEntity.get<Component1::id>();
but then I have to make sure to use different ids for each component and that's bad for mantainance.
Is there a way to "map" a type to a value of that type using magic (i.e. templates), so that
myEntity.get<Component1>()
works as expected?
I'd also like to have O(1) access to a component since the myEntity::get<T>
is used very often (not that with 15-20 components makes sense talking about complexity anyway) but that's not mandatory.
回答1:
I very nearly asked the same question: for me boost::fusion::map
or boost::fusion::set
are overkill and I really don't like the extra-long template parameter lists and having to set a macro if I have more than 10 in my container. I went with something like this:
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>();
回答2:
Consider using a boost::fusion::map, this allows you to map types to values, for example:
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
I think the above is correct, sorry about brevity, not easy on iPhone!
EDIT: Actually, as pointed out by @Eugine below, boost::fusion::set
is a better match, similar thing to above:
typedef fusion::set<Component1, Component2, etc.> set_t;
set_t s(Component1(), Component2());
// to access
at_key<Component1>(s); // reference to instance of Component1
回答3:
It might be possible to use a CRTP-based solution.
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> {
};
Note that I aven't actually tried this, but I think it should work. However, spamming get()
functions like this typically shows that, well, your class design is really kind of poor.
回答4:
You could do it like this:
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);
}
This does a linear lookup but that's O(n) in compile-time (actually in terms of template instantiations) only; it's O(1) in runtime so perhaps that's acceptable to you. Note that some compilers have O(n) template lookup so you may end up in O(n^2) compile-time; I believe C++11 will require that compilers do constant-time template lookup. You can also avoid some instantiations by not eagerly instantiating the recursion, e.g. using Boost.MPL. I avoided this for brevity and clarity.
The above relies on advanced features of Boost Tuple which aren't available for std::tuple
(C++11). However I believe it wouldn't be too hard to implement lookup
in C++11 using variadic templates (left as an exercise to the reader ;). You'd avoid the eager instantiation without using Boost.MPL, too.
Other remarks:
- This requires that each component be of a different type.
- Inside your member functions you'll lose easy access to each component since you can't name them directly but have to resort to calling
get
. I suppose you could still use them as individual members, and use a tie-tuple inside Entity::get
to return the proper reference. This would come at a small cost to maintenance (change Entity::get
everytime you add/remove a component). This also left as an exercise to the reader (don't forget to take into account that the new keys will be of the form Component&
!).
回答5:
If you make your components available to everyone and their dog anyways, why not simply make them public? Mission accomplished without copy&paste.
回答6:
In many situation, this haves drawbacks, but in you case, may be a "selective decay" can suffice:
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; }
};
right now, you can use * as a "component selector" as
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:
I might be wrong, but it seems to me what you're looking for is Boost.Variant
But, if you want ALL the components to be available and have one instance of each component, then variants are not for you.
回答8:
Look at using typeindex and typeid.
You could add components to a map by template type with its typeid being the map key. You can then get components from the map by type.
#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;
};