Question

I want to be able to specifiy a type in as a string and create that type in C++. I know C++ doesn't support that directly but whats the best way to approach this?

I currently have an xml that contains information but I want to expand that to include components.

<entity>
   <component>ComponentA</component>
   <component>ComponentB</component>
</entity>

I have a generic factory that takes in these xml's and builds up the entities. I want to be able to avoid if("componentA") { new ComponentA; } in favour of something more generic. Primarily as the components will be defined by the client and the factory is not.

I thought that components could register themselves with the factory and store a map, but that would require holding a copy of all the components which I'd like to avoid.

I crossplatform solution would be preferable.

Was it helpful?

Solution

AFAIK, at least with general C++, there's no implicit way to create a class using just a string. However, there is another mechanism I have used in the past.

Firstly, you define the notion of a component:

class Component /* or IComponent if you're feeling adventurous - we may not have interfaces in C++, but dammit we like 'em! */
{
protected:
    Component() { };

public:
    virtual ~Component() = 0 { };
}; // eo class Component

And a notion of a some kind of creator:

class ComponentCreator
{
protected:
    Component() { };

public:
    virtual ~ComponentCreator() = 0 { };
    virtual Component* create() const = 0;  // Might want to use smart-pointers here - this is for illustrative purposes only.
}; // eo class ComponentCreator

Ok, we have the basics now we need a factory that can have these creators registered against it:

class Factory
{
private:
    std::map<std::string, ComponentCreator*> _creators;

public:
    Factory() : _creators(new std::map<std::string, ComponentCreator*>();
    {
    };

    ~Factory()
    {
       // cleanup of _creators ommited.
    };

    // call to register a creator
    void register(const std::string& name, ComponentCreator* creator)
    {
        // normally you'd put checks to see if it exists etc.
        _creators[name] = creator;
    }; // eo register


    // call to create an instance
    Component* create(const std::string& name)
    {
        std::map<std::string, ComponentCreator*>::const_iterator cit(_creators.find(name));
        if(cit != _creators.end())
            return cit->create();
        else
            return NULL; // or nullptr
    }; // eo create
}; // eo class Factory

Declare your classes thusly (I will do just one):

class ComponentA : public Component { /* implementation */ };

And don't forget the creator:

class ComponentCreatorForA : public ComponentCreator
{
public:
    virtual Component* create() const { return new ComponentA(); };
}; // eo class ComponentCreatorForA

During initialisation of your program, you register component creators:

factory.register("componentA", new ComponentCreatorForA());
factory.register("componentB", new ComponentCreatorForB());

And later on, we can then create components by name:

Component* component = factory.create("componentA");

Notes:

  • This approach assumes components are known at compile-time. If not one could introduce a plugin-architecture so that additional DLLs can register their components via the factory on start-up so you could make it extensible without having to re-deploy everything.

  • In the real world we'd use smart pointers of some such, and typedef a lot of that stuff away to make it easier on typing!

OTHER TIPS

You have to store meta-information about your component classes. A possible solution might use templates:

// the component interface
class BaseComponent {...}

// structure containing meta-information
template<typename ComponentType>
struct tComponentMeta {
    typedef ComponentType type;
    std::string componentTypeName;

    tComponentMeta() : componentTypeName("no valid component type") {}
} 

// providing run time type information 
// (optional, but i like it if the component know about their type
template<typename ComponentType>
class TComponent : public BaseComponent
{
     tComponentMeta<ComponentType> metaInfo;
     TComponent(const std::string& uniqueTypeName) {...};
}

class ConcreteComponent : public TComponent<ConcreteComponent>
{
   ...
}

Now the client has to define the specialized tComponentMeta for the ConcreteComponent type. This can be achieved by adding the following code after the class declaration of ConcreteComponent:

template <>
struct tComponentMeta {
    typedef ConcreteComponent type
    tComponentMeta() : componentTypeName("ConcreteComponent") {}
}

So if you clients define the template specialization for the components you can provide them with a generic factory that has a template method of the following type, that also has to be invoked by the client designing the component:

...
template<typename ComponentType>
registerComponentType() {
    tComponentMeta<ComponentType> metaInfo;
    nameToMetaMap.put(metaInfo.name, metaInfo)
}

with these building blocks you can generate components in a generic way by forcing the client to provide tComponentMeta specializations of their components and register the component-type at your generic factory.

This code has not been tested by myself so you can assume that there are some syntax errors but I hope the idea is clear.

Due to the nature of templates that approach shall also work in a plug-in architecture that uses DLLs.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top