7

I need a way to instantiate objects based on its class name passed by as a std::string. This is working right now, but need to be generalized:

void* create(std::string name) {
    if(name == "classOne") return new ClassOne();
    else if(name == "classTwo") return new ClassTwo();
    /* ... */
}

What i do not have:

  • Control over the classes to be instantiated: could be thirty party classes. No changes may be done to this classes (i.e. base ancestor, polymorphic creator method, etc...)
  • Full class name listing: more classes could be added later and should not incur in changes to this factory.
  • Wrappers around the classes to be instantiated: As a result of the previous two points.

Anything else is a go.

The best use case scenario will be:

int main() {
    void *obj = create("classTree"); // create object based on the string name
    /* ... */
    // once we know by context which specific class we are dealing with
    ClassTree *ct = (ClassTree*)obj; // cast to appropiate class
    std::cout << ct->getSomeText() << std::endl; // use object
}

As a side, and maybe irrelevant note, take in account the object to be instantiated may come from a class or a struct.

ADDED INFORMATION

I see more context is needed. Here is my particular use case, simplified:

// registration mechanism
int main() {
    std::map< std::string, void(*func)(std::string, void*) > processors; // map of processors by class name
    processors["ClassFour"] = (void(*)(std::string, void*)) &classFourMessageProcessor; // register processor (cast needed from specific to generic)
}
// function receiving string messages
void externalMessageHandler(std::string msg) {
    std::string objType = extractTypeFromMessageHeader(msg); // extract type from message
    // now that we know what we are dealing with, create the specific object
    void *obj = create(objType); // << creator needed
    processors[objType](msg, obj); // dispatch message to process
}
// previously registered message processor
void classFourMessageProcessor(std::String msg, ClassFour *obj) {
    std::string streetAddress = msg.substr(10, 15); // knowing the kind of message we can extract information
    obj->moveTheEtherTo(streetAddress); // use the created object
}

ADDED INFORMATION

I am using C++11 with the latest GNU compiler.

yiown
  • 153
  • 2
  • 7
  • 1
    y not just instantiate the right class "once we know which specific class we are dealing with" and do the stuff you would be doing with that `void*` afterwards? – Kal Sep 26 '13 at 19:02
  • 1
    @Kal You might need to use `obj` before knowing what class it is. – SirGuy Sep 26 '13 at 19:03
  • 1
    This is not the normal way of creating a factory. It will not scale well due to the large if statement block. See http://stackoverflow.com/questions/5120768/how-to-implement-the-factory-pattern-in-c-correctly – Bathsheba Sep 26 '13 at 19:04
  • I don't think you can allow other types to be added to your factory without modifying the factory's code itself. C++ lacks runtime reflection. unlike C#, for example. – Vittorio Romeo Sep 26 '13 at 19:06
  • if u for sum reason need the `void*` b4 knowing what it is, u can just have a `map` and make each class call a function to register itself along with its name like `register_class("Myself")` and have the factory add it to the map and look it up when it wants to create one. but that is bad design – Kal Sep 26 '13 at 19:08
  • 1
    Returning `void*` seems anti-C++. You really want to return, at the very least, a pointer to some kind of abstract base class. – tadman Sep 26 '13 at 19:48
  • Can you create the object in your processor function? If not, you can have another map, mapping class name to a function returning void* . The function allocates an object and returns it as void* –  Sep 26 '13 at 19:57

4 Answers4

5

You can just store a factory function for every class type. An easy way is to use a template

template <typename T>
void* creator() {
  return new T();
}

and store those in the map as well (i.e. "ClassFour" links to creator<ClassFour> and to ClassFourMessageProcessor).

Edit: for clarification, processors becomes a

typedef void* (*CreatorFunc)();
typedef void (*ProcessorFunc)(std::string, void*);

typedef std::pair<CreatorFunc, ProcessorFunc> Entry;
std::map< std::string, Entry > processors;

Adding a new class is as simple as

processors["SomeClass"] = Entry(creator<SomeClass>, ClassFourMessageProcessor);
Alexander Gessler
  • 45,603
  • 7
  • 82
  • 122
  • Not sure what purpose this serves. How is this any easier than `new ClassOne()`? – tadman Sep 26 '13 at 19:47
  • This does not fit his requirements of closed for modification. Every new class would have the requirement of having this method. – Will Custode Sep 26 '13 at 19:48
  • It allows the factories to be also kept in the list, thus making the create function independent of the classes registered. It avoids the hand-written branching for all supported class types. It is also a pretty standard pattern. – Alexander Gessler Sep 26 '13 at 19:49
  • @WilliamCustode this `create()` is NOT part of the class. It's a templated global function. – Adam Sep 26 '13 at 19:50
  • @WilliamCustode it does not impose any need on changing the classs. It exactly does what your sample code asked for: it generalizes hand-written branches for all supported classes. – Alexander Gessler Sep 26 '13 at 19:51
  • the problem i see is, the object created will be stored in the map and passed by with every message of that type. if you have a way to "clone" the object and send a new one for every message, then the solution is good. – yiown Sep 26 '13 at 19:55
  • The object isnot stored in the map - the function that creates one is. Every time a message is processed, a new object instance is created by invoking the creator function. – Alexander Gessler Sep 26 '13 at 19:57
0

Here's one take:

  1. For each class, create a createInsrance() function (not a method) that instantiate an instance and return a pointer cast to void*. Note this function is not part of the class - just a plain function.
  2. Maintain a map of string to function pointer to createInstance type function.
  3. "Register" each of the relevant classes in the map - add the string-function pointer pair to the map.
  4. Now the generic create will search for the string in the map and invoke the specific createInstane, returning the new instance's ptr.

Now you made no changes to the classes, and can add more classes without reprogramming the factory. You may probably put at least #1 as a template - be sure to make the compiler instantiate the specific implementation.

Avi Perel
  • 422
  • 3
  • 8
  • should work but it is not as generic as i want. i am looking for a way to do it once and for all, then forget about the matter. thanks anyway. – yiown Sep 26 '13 at 19:53
  • Assuming you want to be able to add new classes in later versions, how would you expect the compiler/runtime code to "know" the "name" of the new class being added? – Avi Perel Sep 26 '13 at 19:59
  • Having to define a function for every class is too much burden in this case, instead a generic way to make the compiler do so is preferable. – yiown Sep 26 '13 at 20:13
0

maybe the following aproach with a lookup table will be a nice solution.

(Note: I don't know wich compiler are you using, so this solution is for c++03, you could take unordered_map instead map if you are using a compiler with c++11 support)

(Note 2: You could use smart pointers too, and take care of the returns values, whit this example I only wants to show an aproach)

#include <iostream>
#include <string>
#include <map>
#include <vector>


struct ClassMaker
{
    virtual void* getInstance() const = 0;
    virtual ~ClassMaker() {}
};

class Factory
{
private:
    std::map<std::string, ClassMaker*> lookupTable;
    typedef typename std::map<std::string, ClassMaker*>::iterator Iterator;
public:
    void addClass(const std::string& key, ClassMaker* const newClassMaker)
    {
        lookupTable[key] = newClassMaker;
    }

    void* create(const std::string& key)
    {
        void* result = NULL;
        Iterator it = lookupTable.find(key);
        
        if(it != lookupTable.end())
            result = (it->second)->getInstance();
        return result;
    }

    void releaseTable()
    {
        for (Iterator it = lookupTable.begin(); it != lookupTable.end(); ++it)
            delete it->second;
    }
};

struct IntCreator : public ClassMaker
{
    void* getInstance() const
    {
        return new int;
    }

};

struct StringCreator : public ClassMaker
{
    void* getInstance() const
    {
        return new std::string;
    }

};



int main()
{
    Factory myFactory;
    myFactory.addClass("int", new IntCreator);
    myFactory.addClass("string", new StringCreator);
    
    int* myInt = reinterpret_cast<int*>(myFactory.create("int"));
    *myInt = 10;
    std::cout<< *myInt << std::endl;
    delete myInt;
    myFactory.releaseTable();
    return 0;
}
Guillaume Jacquenot
  • 11,217
  • 6
  • 43
  • 49
hidrargyro
  • 257
  • 1
  • 7
  • Please read the description of the problem. This does not conform to it. Thanks – yiown Sep 26 '13 at 20:14
  • One of your "not have points" is: "Full class name listing: more classes could be added later and should not incur in changes to this factory." With this aproach the Factory never changes, you can add new classes with a single method – hidrargyro Sep 26 '13 at 20:18
  • It is the same as the approach I described, except it does not template the factories and a whole polymorphic class instead of a single factory function is used (which complicates things as it requires memory management for the instances created thereof). To my understanding, this also conforms to the specification, though. – Alexander Gessler Sep 27 '13 at 12:26
-1

Would you consider Boost.MPL? Unlike STL, it allows creation of containers containing types, not instances. Having a map from string to a type would give you desired factory, isn't it?

Yury Schkatula
  • 5,291
  • 2
  • 18
  • 42