Reputation:
I am learning C++ by programing a game and I am stuck with something: I have a code which is starting to be repetitive (I will add a number of other components) and I would like to improve it using templates (I guess), which I don't really know how to use.
Here's the code:
#include <memory>
#include "PositionComponent.h"
#include "VelocityComponent.h"
class ComponentManager
{
public:
ComponentManager();
void addComponent(Entity id, PositionComponent component);
void addComponent(Entity id, VelocityComponent component);
private:
std::map<Entity,std::shared_ptr<PositionComponent> > m_positionComponents;
std::map<Entity,std::shared_ptr<VelocityComponent> > m_velocityComponents;
};
and
#include "ComponentManager.h"
ComponentManager::ComponentManager()
{
}
void ComponentManager::addComponent(Entity id, PositionComponent component)
{
if (m_positionComponents.count(id) > 0)
{
Tools::log("couldn't add component");
}
// else if entity doesn't exist..
else
{
m_positionComponents[id] = std::make_shared<PositionComponent>(component);
}
}
void ComponentManager::addComponent(Entity id, VelocityComponent component)
{
if (m_velocityComponents.count(id) > 0)
{
Tools::log("couldn't add component");
}
// else if entity doesn't exist..
else
{
m_velocityComponents[id] = std::make_shared<VelocityComponent>(component);
}
}
How to make that beautiful with templates?
Thanks!
Upvotes: 0
Views: 141
Reputation: 10780
I think using a template here is fine, and other options definitely exist. Here's the template solution I would try:
template <class T>
void ComponentManager::addComponent(Entity id, T component)
{
using MapType = std::map<Entity,std::shared_ptr<T> > &;
auto allMaps = std::tie(m_positionComponents, m_velocityComponents);
auto & tMap = std::get<MapType>(allMaps);
if (tMap.count(id) > 0)
{
Tools::log("couldn't add component");
}
// else if entity doesn't exist..
else
{
tMap[id] = std::make_shared<T>(component);
}
}
This isn't tested at all, so I don't promise it will compile as is.
The overload of std::get
used here requires c++-14
.
Upvotes: 1
Reputation: 206717
How to make that beautiful with templates?
Solution 1
Add a private
member function template to ComponentManager
.
template <typename Component>
void addComponent(Entity id,
Component component,
std::map<Entity,std::shared_ptr<Component> >& components)
{
if (components.count(id) > 0)
{
Tools::log("couldn't add component");
}
// else if entity doesn't exist..
else
{
components[id] = std::make_shared<Component>(component);
}
}
and use it as:
void ComponentManager::addComponent(Entity id, PositionComponent component)
{
this->addComponent(id, component, m_positionComponents);
}
void ComponentManager::addComponent(Entity id, VelocityComponent component)
{
this->addComponent(id, component, m_velocityComponents);
}
The drawback of this approach is that for every type of Component
you want to support, you'll need to add another public
member function.
Solution 2
You can have only one public
member function template to add components to the appropriate collection. Then you'll need to add code to get the right collection given the component type. One way to that would be to use a tag dispatch mechanism.
class ComponentManager
{
public:
ComponentManager();
template <typename Component>
void addComponent(Entity id, Component component)
{
std::map<Entity,std::shared_ptr<Component> >& components = getCollection(tag<Component>());
if (components.count(id) > 0)
{
Tools::log("couldn't add component");
}
// else if entity doesn't exist..
else
{
components[id] = std::make_shared<Component>(component);
}
}
private:
template <typename Component> struct tag {};
std::map<Entity,std::shared_ptr<PositionComponent> >& getCollection(tag<PositionComponent>)
{
return m_positionComponents;
}
std::map<Entity,std::shared_ptr<PositionComponent> >& getCollection(tag<VelocityComponent>)
{
return m_velocityComponents;
}
std::map<Entity,std::shared_ptr<PositionComponent> > m_positionComponents;
std::map<Entity,std::shared_ptr<VelocityComponent> > m_velocityComponents;
};
With this approach, you will still need to add another member function to the class for each type of Component
you wish to support but they will be in the private
section of the class, not public
.
Upvotes: 0
Reputation: 218268
You may create a template class that handles one generic type
template <typename T>
class ComponentManagerT
{
public:
void addComponent(Entity id, T component)
{
if (m_components.count(id) > 0)
{
Tools::log("couldn't add component");
}
// else if entity doesn't exist..
else
{
m_components[id] = std::make_shared<T>(component);
}
}
// ...
private:
std::map<Entity, std::shared_ptr<T>> m_components;
};
And a class to regroup all those managers
struct ComponentManager
{
ComponentManagerT<PositionComponent> m_positionComponentManager;
ComponentManagerT<VelocityComponent> m_velocityComponentManager;
};
Upvotes: 0