Могу ли я получить доступ к «компоненту» по типу? - PullRequest
3 голосов
/ 06 мая 2011

У меня есть такой класс:

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, это круто, но позволяет получить доступ, используя целое число в качестве ключа. Я мог бы использовать общедоступное статическое целое число const в каждом классе Component * и получить доступ следующим образом:

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

но тогда я должен убедиться, что для каждого компонента используются разные идентификаторы, и это плохо для обслуживания.

Есть ли способ «сопоставить» тип со значением этого типа, используя магию (то есть шаблоны), чтобы myEntity.get<Component1>() работает как положено?

Я также хотел бы иметь O (1) доступ к компоненту, поскольку myEntity::get<T> используется очень часто (не то, что с 15-20 компонентами имеет смысл говорить о сложности в любом случае), но это не обязательно.

Ответы [ 8 ]

5 голосов
/ 06 мая 2011

Рассмотрите возможность использования 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
3 голосов
/ 06 мая 2011

Вы можете сделать это так:

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). Однако я полагаю, что не будет слишком сложно реализовать lookup в C ++ 11 с использованием шаблонов с переменными параметрами (оставлено в качестве упражнения для читателя;). Вы бы также избежали активного создания экземпляров, не используя Boost.MPL.

Другие замечания:

  • Для этого требуется, чтобы каждый компонент был другого типа.
  • Внутри ваших функций-членов вы потеряете легкий доступ к каждому компоненту, так как вы не можете назвать их напрямую, но должны прибегнуть к вызову get. Я полагаю, что вы все еще можете использовать их в качестве отдельных членов и использовать связующий кортеж внутри Entity::get, чтобы вернуть правильную ссылку. Это потребует небольших затрат на обслуживание (меняйте Entity::get каждый раз, когда вы добавляете / удаляете компонент). Это также оставило читателю упражнение (не забудьте учесть, что новые ключи будут иметь вид Component&!).
3 голосов
/ 06 мая 2011

Возможно, можно использовать решение на основе 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 голосов
/ 23 июля 2012

Я почти задал один и тот же вопрос: для меня 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>();
1 голос
/ 12 сентября 2011

Во многих ситуациях это имеет недостатки, но в вашем случае может оказаться достаточным «выборочный распад»:

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 */ }
1 голос
/ 06 мая 2011

Если вы в любом случае делаете свои компоненты доступными для всех и их собак, почему бы просто не сделать их общедоступными? Миссия выполнена без копирования и вставки.

0 голосов
/ 19 февраля 2017

Посмотрите на использование 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;
};
0 голосов
/ 06 мая 2011

Я могу ошибаться, но мне кажется, что вы ищете: Boost.Variant

Но если вы хотите, чтобы ВСЕ компоненты были доступны и имели один экземпляр каждого компонента, тогда варианты не для вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...