3
\$\begingroup\$

I'm trying to map a components type to the component value itself in a map. The following code all works properly. My main question is this. Is storing the map key as std::type_info* the best way to accomplish what I'm doing? I'm also not digging the ugly static_cast<T*> I'm doing during the usage part.

Entity.h

#pragma once
#include "IComponent.h"
#include <map>
#include <typeinfo>

class Entity
{
public:
    Entity(void);
    ~Entity(void);
    void AddComponent(IComponent* component);
    IComponent* GetComponent(const std::type_info* typeInfo);
private:
    std::map<const std::type_info*, IComponent*> components;
};

Entity.cpp

#include "Entity.h"

Entity::Entity(void)
{
}

Entity::~Entity(void)
{
    for (auto &mapValue : components)
    {
        delete mapValue.second;
    }
}

void Entity::AddComponent(IComponent* component)
{
    const type_info* info = &typeid(*component);
    components.insert (std::pair<const std::type_info*,IComponent*>(info, component));
}

IComponent* Entity::GetComponent(const std::type_info* typeInfo)
{
    return components.at(typeInfo);
}

Example Usage

IComponent* baseComponent = entity->GetComponent(&typeid(PerkList));
PerkList* perkList = static_cast<PerkList*>(baseComponent);
\$\endgroup\$
6
  • \$\begingroup\$ I had the same thoughts, but it works. I -think- it has to do with how types might be defined during program execution. Such as that the type info for a particular class will always be on the stack. Although I am pulling that train of thought out of the air. \$\endgroup\$
    – TimeEchoes
    Mar 16, 2015 at 3:33
  • \$\begingroup\$ Never mind, just noticed that typeid return and object with static storage. It will last for the lifetime of the program. \$\endgroup\$
    – glampert
    Mar 16, 2015 at 3:35
  • 1
    \$\begingroup\$ Did you take a look at type_index? Seems more adequate for what you are doing and can be used with an unordered_map. (C++11) \$\endgroup\$
    – glampert
    Mar 16, 2015 at 3:37
  • \$\begingroup\$ That's actually new to me. I'll look that up right now. \$\endgroup\$
    – TimeEchoes
    Mar 16, 2015 at 3:40
  • 1
    \$\begingroup\$ @glampert It seems as though type_index is exactly what I was looking for and is meant as a clean wrapper for all the type_info usage in this case. Thank you! \$\endgroup\$
    – TimeEchoes
    Mar 16, 2015 at 3:45

1 Answer 1

6
\$\begingroup\$

type_info:

Using the type_info reference is a viable approach, but it can be made better with type_index in conjunction with an unordered_map.

static_cast:

I don't think there is a way around the cast here. You can make it slightly less verbose by defining a template helper method that wraps the type cast:

template<class T>
T * Entity::GetComponent()
{
    return static_cast<T *>(components.at(&typeid(T)));
}

// Usage:
PerkList * perkList = entity->GetComponent<PerkList>();

One nice extra of this approach is that you can replace the static_cast inside with a dynamic_cast during debug/dev builds and check that the types are self-consistent. Then add a preprocessor to use the faster static_cast for a "release" build if you need to squeeze every cycle.

Pointers:

Returning a raw pointer to IComponent is questionable to say the least. A raw pointer conveys no ownership, so it is very easy to leak or delete the objects multiple times when you have raw pointers flying around like this. You should take a look at shared_ptr and weak_ptr before further expanding your implementation. Or perhaps return just a reference (IComponent &) if the caller of GetComponent() should not own the object.

Minor details:

  • Don't add void to the parameter list of functions/methods that take no parameters. This is a C-ish style. C++ does not require that; () is just as good. So avoid the unnecessary verbosity.

  • Empty destructors/constructors should be omitted or made defaults.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I liked your added portion on the pointers. I know my implementation of them was a bit haphazard. Good advice on returning a reference instead of a raw pointer as you're indeed correct about ownership. The entity 'owns' the pointer and that's it, everything else is just borrowing it for use through GetComponent. \$\endgroup\$
    – TimeEchoes
    Mar 16, 2015 at 4:20
  • \$\begingroup\$ It's also worth noting: There is no guarantee that the same std::type_info instance will be referred to by all evaluations of the typeid expression on the same type, although std::type_info::hash_code of those type_info objects would be identical, as would be their std::type_index. Source: en.cppreference.com/w/cpp/language/typeid \$\endgroup\$
    – Dan
    Feb 2, 2021 at 5:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.