Un commentaire:
L'implémentation d'Artemis est intéressante. J'ai proposé une solution similaire, sauf que j'ai appelé mes composants "Attributs" et "Comportements". Cette approche de la séparation des types de composants a très bien fonctionné pour moi.
En ce qui concerne la solution:
le code est facile à utiliser, mais sa mise en œuvre risque d’être difficile à suivre si vous n’êtes pas expérimenté en C ++. Alors...
L'interface souhaitée
Ce que j'ai fait est d'avoir un référentiel central de tous les composants. Chaque type de composant est associé à une chaîne donnée (qui représente le nom du composant). Voici comment vous utilisez le système:
// Every time you write a new component class you have to register it.
// For that you use the `COMPONENT_REGISTER` macro.
class RenderingComponent : public Component
{
// Bla, bla
};
COMPONENT_REGISTER(RenderingComponent, "RenderingComponent")
int main()
{
// To then create an instance of a registered component all you have
// to do is call the `create` function like so...
Component* comp = component::create("RenderingComponent");
// I found that if you have a special `create` function that returns a
// pointer, it's best to have a corresponding `destroy` function
// instead of using `delete` directly.
component::destroy(comp);
}
La mise en oeuvre
L'implémentation n'est pas si mauvaise, mais c'est quand même assez complexe. cela nécessite une certaine connaissance des modèles et des pointeurs de fonction.
Remarque: Joe Wreschnig a fait quelques remarques intéressantes dans les commentaires, notamment sur le fait que mon implémentation précédente reposait sur trop d'hypothèses sur l'efficacité du compilateur dans l'optimisation du code. le problème n'était pas préjudiciable, mais je m'en foutais aussi. J'ai également remarqué que l'ancienne COMPONENT_REGISTER
macro ne fonctionnait pas avec les modèles.
J'ai changé le code et maintenant tous ces problèmes devraient être résolus. La macro fonctionne avec des modèles et les problèmes soulevés par Joe ont été résolus: il est maintenant beaucoup plus facile pour les compilateurs d’optimiser le code inutile.
composant / composant.h
#ifndef COMPONENT_COMPONENT_H
#define COMPONENT_COMPONENT_H
// Standard libraries
#include <string>
// Custom libraries
#include "detail.h"
class Component
{
// ...
};
namespace component
{
Component* create(const std::string& name);
void destroy(const Component* comp);
}
#define COMPONENT_REGISTER(TYPE, NAME) \
namespace component { \
namespace detail { \
namespace \
{ \
template<class T> \
class ComponentRegistration; \
\
template<> \
class ComponentRegistration<TYPE> \
{ \
static const ::component::detail::RegistryEntry<TYPE>& reg; \
}; \
\
const ::component::detail::RegistryEntry<TYPE>& \
ComponentRegistration<TYPE>::reg = \
::component::detail::RegistryEntry<TYPE>::Instance(NAME); \
}}}
#endif // COMPONENT_COMPONENT_H
composant / detail.h
#ifndef COMPONENT_DETAIL_H
#define COMPONENT_DETAIL_H
// Standard libraries
#include <map>
#include <string>
#include <utility>
class Component;
namespace component
{
namespace detail
{
typedef Component* (*CreateComponentFunc)();
typedef std::map<std::string, CreateComponentFunc> ComponentRegistry;
inline ComponentRegistry& getComponentRegistry()
{
static ComponentRegistry reg;
return reg;
}
template<class T>
Component* createComponent() {
return new T;
}
template<class T>
struct RegistryEntry
{
public:
static RegistryEntry<T>& Instance(const std::string& name)
{
// Because I use a singleton here, even though `COMPONENT_REGISTER`
// is expanded in multiple translation units, the constructor
// will only be executed once. Only this cheap `Instance` function
// (which most likely gets inlined) is executed multiple times.
static RegistryEntry<T> inst(name);
return inst;
}
private:
RegistryEntry(const std::string& name)
{
ComponentRegistry& reg = getComponentRegistry();
CreateComponentFunc func = createComponent<T>;
std::pair<ComponentRegistry::iterator, bool> ret =
reg.insert(ComponentRegistry::value_type(name, func));
if (ret.second == false) {
// This means there already is a component registered to
// this name. You should handle this error as you see fit.
}
}
RegistryEntry(const RegistryEntry<T>&) = delete; // C++11 feature
RegistryEntry& operator=(const RegistryEntry<T>&) = delete;
};
} // namespace detail
} // namespace component
#endif // COMPONENT_DETAIL_H
composant / composant.cpp
// Matching header
#include "component.h"
// Standard libraries
#include <string>
// Custom libraries
#include "detail.h"
Component* component::create(const std::string& name)
{
detail::ComponentRegistry& reg = detail::getComponentRegistry();
detail::ComponentRegistry::iterator it = reg.find(name);
if (it == reg.end()) {
// This happens when there is no component registered to this
// name. Here I return a null pointer, but you can handle this
// error differently if it suits you better.
return nullptr;
}
detail::CreateComponentFunc func = it->second;
return func();
}
void component::destroy(const Component* comp)
{
delete comp;
}
Étendre avec Lua
Je dois noter qu'avec un peu de travail (ce n'est pas très difficile), cela peut être utilisé pour travailler de manière transparente avec des composants définis en C ++ ou en Lua, sans jamais avoir à y penser.