0
\$\begingroup\$

I have a setup in my game engine where I can add behavours to game objects. All behaviours are based on the class AbstractBehaviour. To easily get access to behaviours from a game object, I am trying to mimic unity's getComponentOfType<type>().

Now for the question: how can I pass a class type as a parameter to my GetBehaviourOfType() function?

What I am trying to achieve in code:

AbstractBehaviour* GameObject::GetBehaviourOfType(typeid type)
{
    for (int i = 0; i < _behaviours->size(); i++)
    {
        if (dynamic_cast<type>(_behaviours[i]) != NULL)
        {
            return _behaviours[i];
        }
    }
}

Where _behaviours is a vector of abstract behaviours.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ This seems like a bad idea. Why do you use c++ if it's to use one of it's slowest features? If you want to do what unity does, why not do it in unity? \$\endgroup\$
    – Vaillancourt
    Feb 12, 2016 at 13:13
  • \$\begingroup\$ because its a school project where we have to write the game in our own c++ engine. \$\endgroup\$ Feb 12, 2016 at 13:14
  • 1
    \$\begingroup\$ I think Unreal Engine is in c++, you might want to investigate how they do their thing! \$\endgroup\$
    – Vaillancourt
    Feb 12, 2016 at 13:47

2 Answers 2

4
\$\begingroup\$

C++ differs a lot from C# in that it is a much more static language. There is very little support for Runtime Type Inference in C++, so in general I'd avoid going that route and just design my code in a very strongly typed fashion, avoiding abstract types, within reason.

If you must go that way, your best bet would probably be using a template, however, it will still be a compile-time thing only, you can't really pass a type as a normal function parameter.

template<class T>
AbstractBehaviour* GetBehaviourOfType()
{
    for (int i = 0; i < _behaviours->size(); i++)
    {
        if (dynamic_cast<T*>(_behaviours[i]) != nullptr)
        {
            return _behaviours[i];
        }
    }
    return nullptr;
}

// auto behaviour = gameObj.GetBehaviourOfType<PlayerBehaviour>();

If you really need the dynamic/runtime variant, then take a look at something like std::type_info. You might be able to come up with a non-trivial solution using it.

\$\endgroup\$
1
\$\begingroup\$

You can use a hash table for behaviors and access the elements by their typeid:

typedef std::unordered_map<const std::type_info*, AbstractBehaviour*> BehaviourHashTable;

// ...

template<typename T>
T* GetBehaviourOfType(void)
{
    if (_behaviours.count(&typeid(T)) != 0){
        return static_cast<T*>(_behaviours[&typeid(T)]);
    }

    return nullptr;
}

This is much faster than using dynamic_cast.

Example (where RunBehaviour is a sub-class):

AbstractBehaviour* run = GetBehaviorOfType<RunBehaviour>();

Insert into the table with:

_behaviours[&typeid(*behaviour)] = behaviour;
\$\endgroup\$
1
  • \$\begingroup\$ Impossible to affirm that the hash-table approach is faster than dynamic_cast without knowing your compiler internals. The compiler might very well be using a hash-table internally already! Also, your lookup of the table is counter-intuitive. In the worst case it will perform two lookups, one for count() and again for operator[]. Better to just use find() and test the iterator against end(), then dereference it. \$\endgroup\$
    – glampert
    Feb 12, 2016 at 16:44

Not the answer you're looking for? Browse other questions tagged .