开发者

Dynamically creating an instance of a class from a string containing the class name in C++

Lets say I have a base class with 100 children:

class Base { 
  virtual void feed();
  ...   
};
class Child1 : public Base {
  void feed();  //specific procedure for feeding Child1
  ... 
};
...
class Child100 : public Base { 
  void feed();  //specific procedure for feeding Child100
  ...
};

At runtime I want to read a file that contains which children to create and feed. Lets say I've read the file and the vector of strings "names" contains the names of the child classes (ie. Child1, Child4, Child99). Now I'm going to iterate through these strings, create an instance of the specific child, and feed it with its specific feeding procedure:

vector<Base *> children;    
for (vector<string>::iterator it = names.begin(); it != names.end(); ++it) {
  Base * child = convert_string_to_instance(*it)       
  child->feed()
  children.push_back(child);
}

How would I create the function convert_string_to_instance() such that if it takes in the string "Child1" it returns a "new Child1", if the string argument is "Child4" it returns a "new Child4", etc

<class C *> convert_string_to_instance(string inName) {
  // magic happens
  r开发者_StackOverflow社区eturn new C;  // C = inName

  // <brute force?>
  // if (inName == "Child1")
  //   return new Child1;
  // if (inName == "Child2")
  //   return new Child2;    
  // if (inName == "Child3")
  //   return new Child3;    
  // </brute force>
  }


C++ does not provide a method for dynamic construction of class instances like this. However, you may be able to use code generation to generate the "brute force" code (like you showed above) from a list of classes. Then, #include the generated code in your convert_string_to_instance method.

You can also set up your project build system to rebuild the generated code anytime the list of classes changes.


I asked a question entitled automatic registration of object creator function with a macro that has the following example program that runs:

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

struct Object{ virtual ~Object() {} }; // base type for all objects

struct ObjectFactory {
  static Object* create(const std::string& id) { // creates an object from a string
    const Creators_t::const_iterator iter = static_creators().find(id);
    return iter == static_creators().end() ? 0 : (*iter->second)(); // if found, execute the creator function pointer
  }

 private:
  typedef Object* Creator_t(); // function pointer to create Object
  typedef std::map<std::string, Creator_t*> Creators_t; // map from id to creator
  static Creators_t& static_creators() { static Creators_t s_creators; return s_creators; } // static instance of map
  template<class T = int> struct Register {
    static Object* create() { return new T(); };
    static Creator_t* init_creator(const std::string& id) { return static_creators()[id] = create; }
    static Creator_t* creator;
  };
};

#define REGISTER_TYPE(T, STR) template<> ObjectFactory::Creator_t* ObjectFactory::Register<T>::creator = ObjectFactory::Register<T>::init_creator(STR)

namespace A { struct DerivedA : public Object { DerivedA() { std::cout << "A::DerivedA constructor\n"; } }; }
REGISTER_TYPE(A::DerivedA, "A");

namespace B { struct DerivedB : public Object { DerivedB() { std::cout << "B::DerivedB constructor\n"; } }; }
REGISTER_TYPE(B::DerivedB, "Bee");

namespace C { struct DerivedC : public Object { DerivedC() { std::cout << "C::DerivedC constructor\n"; } }; }
REGISTER_TYPE(C::DerivedC, "sea");

namespace D { struct DerivedD : public Object { DerivedD() { std::cout << "D::DerivedD constructor\n"; } }; }
REGISTER_TYPE(D::DerivedD, "DEE");

int main(void)
{
  delete ObjectFactory::create("A");
  delete ObjectFactory::create("Bee");
  delete ObjectFactory::create("sea");
  delete ObjectFactory::create("DEE");
  return 0;
}

compile and run output is:

> g++ example2.cpp && ./a.out
A::DerivedA constructor
B::DerivedB constructor
C::DerivedC constructor
D::DerivedD constructor


If you have a lot of classes, you'd usually choose a less brute force approach. A trie or hash_map between class names and factory functions is a good approach.

You can use a codegen approach as suggested by Greg to build this factory table, for example doxygen can parse your source code and output a list of all classes in xml format along with inheritance relationships, so you could easily find all classes deriving from a common "interface" base class.


It sounds like you might be using subclasses for things that should be encoded as fields.

Instead of coding the different behaviour in 100 classes, consider building a look-up table with rules/constants/function-pointers that allow you to implement the proper behaviour from one class.

For example, instead of:

class SmallRedSquare  : public Shape {...};
class SmallBlueSquare : public Shape {...};
class SmallBlueCircle : public Shape {...};
class SmallRedCircle  : public Shape {...};
class BigRedSquare    : public Shape {...};
class BigBlueSquare   : public Shape {...};
class BigBlueCircle   : public Shape {...};
class BigRedCircle    : public Shape {...};

try:

struct ShapeInfo
{
   std::string type;
   Size size;
   Color color;
   Form form;
};

class Shape
{
public:
    Shape(std::string type) : info_(lookupInfoTable(type)) {}

    void draw()
    {
        // Use info_ to draw shape properly.
    }

private:
    ShapeInfo* lookupInfoTable(std::string type) {info_ = ...;}

    ShapeInfo* info_;
    static ShapeInfo infoTable_[];
};

const ShapeInfo Shape::infoTable_[] =
{
    {"SmallRedSquare",  small,  red, &drawSquare},
    {"SmallBlueSquare", small, blue, &drawSquare},
    {"SmallRedCircle",  small,  red, &drawCircle},
    {"SmallBlueCircle", small, blue, &drawCircle},
    {"BigRedSquare",      big,  red, &drawSquare},
    {"BigBlueSquare",     big, blue, &drawSquare},
    {"BigBlueCircle",     big,  red, &drawCircle},
    {"BigRedCircle",      big, blue, &drawCircle}
}

int main()
{
    Shape s1("SmallRedCircle");
    Shape s2("BigBlueSquare");
    s1.draw();
    s2.draw();
}

This idea might not be applicable to your problem, but I figure it couldn't hurt to present it anyway. :-)

My idea is like the Replace Subclass with Fields refactoring, but I go a bit further.


You can abuse the preprocessor and set up some static class members that register your classes with a factory via a hash_map like Ben describes. If you have visual studio, look at how DECLARE_DYNCREATE is implemented in MFC. I've done something similar to implement a class factory. Non-standard for sure but since C++ does not offer any kind of support for this type of mechanism any solution is probably going be non-standard.

Edit

I said in a comment earlier I was working on documenting a scaled down version of something I had done. The scaled down version is still rather large so I posted it here. If there is enough interest I can copy/paste it on this site. Let me know.


This is the skeleton of a horrible, horrible way to do it:

class Factory {
  public:
    virtual Base * make() = 0;
};

template<typename T> class TemplateFactory : public Factory {
  public:
    virtual Base * make() {
      return dynamic_cast<Base *>(new T());
    }
};

map<string, Factory *> factories;

#define REGISTER(classname) factories[ #classname ] = new TemplateFactory<classname>()

Then call REGISTER(classname); for every relevant derived class of Base, and use factories["classname"]->make() to get a new object of type classname. Obvious flaws with the above code as written include massive potential for memory leaks, and the general awfulness of combining macros and templates.


Behold the mighty Boost.

The one thing you have to do in order to use my solution is to add a new member to all your classes, and that is a static const string that contains the name of the class. There are probably other ways to do it too, but that's what I have right now.

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

#include <boost/fusion/container/list/cons.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/view/iterator_range.hpp>

using namespace std;
using boost::fusion::cons;


class Base { virtual void feed(){ } };

class Child1 : public Base{
  void feed(){ }

public:
  static const string name_;
};
const string Child1::name_ = "Child1";

class Child3 : public Base{
  void feed(){ }

public:
  static const string name_;
};
const string Child3::name_ = "Child3";

//...
class Child100 : public Base{

  void feed(){ }

public:
  static const string name_;
};
const string Child100::name_ = "Child100";

// This is probably the ugliest part, but I think it's worth it.
typedef cons<Child1, cons<Child3, cons<Child100> > > MyChildClasses;

typedef vector<Base*> Children;
typedef vector<string> Names;

struct CreateObjects{      // a.k.a convert_string_to_instance() in your example.

  CreateObjects(Children& children, string name) : children_(&children), name_(name){ }

  template <class T>
  void operator()(T& cs) const{

    if( name_ == cs.name_ ){
      cout << "Created " << name_ << " object." << endl;
      (*children_).push_back(new T);
    }else{
      cout << name_ << " does NOT match " << cs.name_ << endl;
    }
  }

  Children* children_;
  string name_;
};

int main(int argc, char* argv[]){

  MyChildClasses myClasses;

  Children children;
  Names names;
  names.push_back("Child1");
  names.push_back("Child100");
  names.push_back("Child1");
  names.push_back("Child100");

  // Extra test.
  // string input;
  // cout << "Enter a name of a child class" << endl;
  // cin >> input;
  // names.push_back(input);

  using namespace boost::fusion;
  using boost::fusion::begin;
  using boost::fusion::for_each;

  for(Names::iterator namesIt = names.begin(); namesIt != names.end(); ++namesIt){

    // You have to know how many types there are in the cons at compile time.
    // In this case I have 3; Child1, Child3, and Child100
    boost::fusion::iterator_range<
      result_of::advance_c<result_of::begin<MyChildClasses>::type, 0>::type,
      result_of::advance_c<result_of::begin<MyChildClasses>::type, 3>::type
      > it(advance_c<0 >(begin(myClasses)),
       advance_c<3>(begin(myClasses)));
    for_each(it, CreateObjects(children, *namesIt));
  }

  cout << children.size() << " objects created." << endl;
  return 0;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜