Ein Kommentar:
Die Artemis-Implementierung ist interessant. Ich habe eine ähnliche Lösung gefunden, mit der Ausnahme, dass ich meine Komponenten "Attribute" und "Verhalten" nannte. Dieser Ansatz der Trennung von Komponententypen hat bei mir sehr gut funktioniert.
In Bezug auf die Lösung:
Der Code ist einfach zu verwenden, aber die Implementierung ist möglicherweise schwer zu befolgen, wenn Sie keine Erfahrung mit C ++ haben. So...
Die gewünschte Schnittstelle
Ich wollte ein zentrales Repository für alle Komponenten haben. Jeder Komponententyp ist einer bestimmten Zeichenfolge zugeordnet (die den Komponentennamen darstellt). So nutzen Sie das System:
// 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);
}
Die Umsetzung
Die Implementierung ist nicht so schlecht, aber immer noch ziemlich komplex. Es erfordert einige Kenntnisse über Vorlagen und Funktionszeiger.
Anmerkung: Joe Wreschnig hat in den Kommentaren einige gute Punkte angesprochen, vor allem, wie meine vorherige Implementierung zu viele Annahmen darüber gemacht hat, wie gut der Compiler den Code optimiert. Das Problem war nicht schädlich, aber es hat mich auch gestört. Mir ist auch aufgefallen, dass das vorherige COMPONENT_REGISTER
Makro mit Vorlagen nicht funktioniert hat.
Ich habe den Code geändert und jetzt sollten alle diese Probleme behoben sein. Das Makro arbeitet mit Vorlagen und die Probleme, die Joe angesprochen hat, wurden behoben: Jetzt ist es für Compiler viel einfacher, unnötigen Code zu optimieren.
komponente / komponente.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
komponente / 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
component / component.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;
}
Mit Lua erweitern
Ich sollte beachten, dass dies mit ein wenig Arbeit (es ist nicht sehr schwer) verwendet werden kann, um nahtlos mit Komponenten zu arbeiten, die entweder in C ++ oder Lua definiert sind, ohne jemals darüber nachdenken zu müssen.