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

后端 未结 7 1992
予麋鹿
予麋鹿 2020-12-31 09:00

Lets say I have a base class with 100 children:

class Base { 
  virtual void feed();
  ...   
};
class Child1 : public Base {
  void feed();  //specific proc         


        
相关标签:
7条回答
  • 2020-12-31 09:51

    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 讨论(0)
  • 2020-12-31 09:53

    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.

    0 讨论(0)
  • 2020-12-31 09:55

    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.

    0 讨论(0)
  • 2020-12-31 09:55

    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.

    0 讨论(0)
  • 2020-12-31 09:55

    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.

    0 讨论(0)
  • 2020-12-31 10:04

    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
    
    0 讨论(0)
提交回复
热议问题