tags:

views:

143

answers:

4

hi i want to create a factory and i would like to use reflection for that. i jast need to create a object with given string and invoke only few known methods how i can do that?

+3  A: 

You will have to roll your own. Usually you have a map of strings to object creation functions.
You will need something like the follwing:

class thing {...};
/*
class thing_A : public thing {...};
class thing_B : public thing {...};
class thing_C : public thing {...};
*/

std::shared_ptr<thing> create_thing_A(); 
std::shared_ptr<thing> create_thing_C(); 
std::shared_ptr<thing> create_thing_D();

namespace {
  typedef std::shared_ptr<thing> (*create_func)();

  typedef std::map<std::string,create_func> creation_map;
  typedef creation_map::value_type creation_map_entry;
  const creation_map_entry creation_map_entries[] = { {"A", create_thing_A}
                                                    , {"B", create_thing_B}
                                                    , {"C", create_thing_C} };
  const creation_map creation_funcs( 
          creation_map_entries, 
          creation_map_entries + sizeof(creation_map_entries)
                               / sizeof(creation_map_entries[0] );
}

std::shared_ptr<thing> create_thing(const std::string& type)
{
  const creation_ma::const_iterator it = creation_map.find(type);
  if( it == creation_map.end() ) {
     throw "Dooh!"; // or return NULL or whatever suits you
  }
  return it->second();
}

There are other ways to do this (like having a map of strings to objects from which to clone), but I think they all boil down to having a map of strings to something related to the specific types.

sbi
The problem of your approach is that you must know the set of classes in compile time, right?
chiccodoro
@chiccodoro: There's little else you can do in C++. One other way (which is, I suppose, how languages do this which come with built-in reflection) is to put special stuff into all classes which automatically registers them with a reflection mechanism. You could, for example, have all classes register a static member factory method with a certain string. Basically, this would boil down to the above, only with a bit more elaborate mechanics that allow adding of classes that weren't known when the mechanism was put into place.
sbi
@sbi isn't this what typeid is for?
David Feurle
@David: I'm not sure what you mean by "this". The `typeid` operator gives you a `std::type_info` object. If you look at that class you'll see that there's almost nothing useful that can be done with `std::type_info` objects. You can compare them, stuff them into a map or a set, and get an implementation-defined string from them (note: a standard-conforming implementation might return empty strings or `"blah"` for all types). You can, however, not use this to _instantiate_ an object of that type - except if you roll your own as shown in the answers to this question.
sbi
@sbi sorry I didn't read your comment correct (parser error :P). I thought you said you want a "static" method that associates a string with a type. So ignore my comment....
David Feurle
@David, I was trying to hint at this: A class could have a static data member, which, in its constructor, registers a factory function and a string with some factory. That factory function could be a static data member of the class. The factory can then use that factory function to create an object of that class when it finds the string registered with it.
sbi
+2  A: 

There is no reflection in C++, directly supported by the standard.

However C++ is sufficiently low-level that you can implement some minimal support for reflection to complete the task at hand.

For the simple task of creating a Factory, you usually use the Prototype approach:

class Base
{
public:
  virtual Base* clone() const = 0;
  virtual ~Base();
};

class Factory
{
public:
  std::unique_ptr<Base> get(std::string const& name);

  void set(std::string const& name, std::unique_ptr<Base> b);

private:
  boost::ptr_map<std::string,Base> mExemplars;
};

Of course, those "known methods" that you are speaking about should be defined within the Base class, which acts as an interface.

Matthieu M.
Yes, cloning of examplars is another way to do this. `+1` from me.
sbi
+1  A: 

There is no reflection in C++, so you should restate your question trying to explain what are the requirements that you would have fulfilled with the reflection part of it.

Depending on your actual constraints and requirements, there are a few things that you can do. The first approach that I would take would be creating an abstract factory where concrete factories can register and provide a simple interface:

class Base {}; // shared base by all created objects
class ConcreteFactoryBase {
public:
   virtual ~ConcreteFactoryBase() {}
   virtual Base* create() const = 0;   // actual construction
   virtual std::string id() const = 0; // id of the types returned
};
class AbstractFactory
{
   typedef std::map<std::string, ConcreteFactory* > factory_map_t;
public:
   void registerFactory( ConcreteFactoryBase* factory ) {
      factories[ factory->id() ] = factory;
   }
   Base* create( std::string const & id ) const {
      factory_map_t::const_iterator it = factories.find( id );
      if ( it == factories.end() ) {
         return 0; // or throw, or whatever makes sense in your case
      }
      return (*it)->create();
   }
   ~AbstractFactory(); // ensure that the concrete factories are deleted
private:
   std::map<ConcreteFactoryBase*> factories;
};

The actual concrete factories can be implemented manually but they can probably be templated, unless the constructors for the different types require different arguments:

template <typename T>
class ConcreteFactory : public ConcreteFactoryBase {
public:
   ConcreteFactory( std::string const & id ) : myid(id) {}
   virtual Base* create() const {
      return new T;
   }
   virtual std::string id() const {
      return myid;
   }
private:
   std::string myid;
};
class Test : public Base {};
int main() {
   AbstracFactory factory;
   factory.register_factory( new ConcreteFactory<Test>("Test") );
}

Optionally you could adapt the signatures so that you can pass arguments to the constructor through the different layers.

Then again, by knowing the actual constraints some other approaches might be better. The clone() approach suggested elsewhere is good (either by actually cloning or by creating an empty object of the same type). That is basically blending the factory with the objects themselves so that each object is a factory of objects of the same type. I don't quite like mixing those two responsabilities but it might be one of the simplest approaches with less code to write.

David Rodríguez - dribeas
A: 

You could use typeid & templates to implement the factory so you won't need strings at all.

#include <string>
#include <map>
#include <typeinfo>

//***** Base *****
class Base
{
public:
    virtual ~Base(){} //needs to be virtual to make typeid work
};

//***** C1 *****
class C1 : public Base
{};

//***** Factory *****
class Factory
{
public:
    template <class T>
    Base& get();
private:
    typedef std::map<std::string, Base> BaseMap;
    BaseMap m_Instances;
};

template <class T>
Base& Factory::get()
{
    BaseMap::const_iterator i = m_Instances.find(typeid(T).name());
    if(i == m_Instances.end()) {
       m_Instances[typeid(T).name()] = T();
    }
    return m_Instances[typeid(T).name()];
}

//***** main *****
int main(int argc, char *argv[])
{
    Factory f;
    Base& c1 = f.get<C1>();
    return 0;
}
David Feurle