【问题标题】:Dynamically creating an instance of a class from a string containing the class name in C++在 C++ 中从包含类名的字符串动态创建类的实例
【发布时间】:2011-06-04 02:23:52
【问题描述】:

假设我有一个有 100 个孩子的基类:

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
  ...
};

在运行时,我想读取一个文件,其中包含要创建和提供的子项。假设我已经阅读了该文件,并且字符串“names”的向量包含子类的名称(即 Child1、Child4、Child99)。现在我将遍历这些字符串,创建特定孩子的实例,并使用其特定的喂养程序喂养它:

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);
}

我将如何创建函数 convert_string_to_instance() 以便如果它接受字符串“Child1”它返回一个“new Child1”,如果字符串参数是“Child4”它返回一个“new Child4”等等

<class C *> convert_string_to_instance(string inName) {
  // magic happens
  return 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++ 中闻起来像折射的动态类。如果没有“蛮力”尝试,我不知道该怎么做。我很想知道怎么做。
  • 基本上是这样的:stackoverflow.com/questions/41453/… 有一些系统可以进行像这样的高级反射:root.cern.ch/drupal/content/reflex,但它们都需要额外的构建步骤来提取元数据
  • 这将是我一段时间以来在 StackOverflow 上看到的最精巧的问题。恰到好处的细节,我喜欢 部分。可悲的是,我认为该主题的变化是唯一的答案。

标签: c++ dynamic new-operator instance


【解决方案1】:

C++ 没有提供像这样动态构造类实例的方法。但是,您可以使用代码生成从类列表中生成“蛮力”代码(如上所示)。然后,#include 在您的convert_string_to_instance 方法中生成代码。

您还可以设置项目构建系统,以便在类列表发生更改时重新构建生成的代码。

【讨论】:

    【解决方案2】:

    我问了一个题为automatic registration of object creator function with a macro 的问题,其中运行了以下示例程序:

    #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;
    }
    

    编译运行输出为:

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

    【讨论】:

      【解决方案3】:

      如果您有很多课程,您通常会选择不那么暴力的方法。类名和工厂函数之间的 trie 或 hash_map 是一个不错的方法。

      您可以使用 Greg 建议的 codegen 方法来构建此工厂表,例如 doxygen 可以解析您的源代码并以 xml 格式输出所有类的列表以及继承关系,因此您可以轻松找到所有派生的类来自一个通用的“接口”基类。

      【讨论】:

      • 投反对票的原因?就此而言,我想知道大多数答案被否决的原因。看起来像除了@Red's 之外的所有东西,很奇怪。
      • 是的,这很有趣不是吗?我正在研究我所做的缩小版本,如果这个线程不会太旧,或者在我的博客上发布。这与@McKay 和@Peter 的示例非常相似。我给 +1 是因为这些都是有用的答案。
      【解决方案4】:

      听起来您可能正在为应该编码为字段的事物使用子类。

      与其在 100 个类中编写不同的行为,不如考虑构建一个包含规则/常量/函数指针的查找表,以便您从一个类中实现正确的行为。

      例如,而不是:

      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 {...};
      

      尝试:

      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();
      }
      

      这个想法可能不适用于您的问题,但我认为无论如何提出它不会有什么坏处。 :-)

      我的想法类似于 Replace Subclass with Fields 重构,但我走得更远。

      【讨论】:

        【解决方案5】:

        您可以滥用预处理器并设置一些静态类成员,这些成员通过像 Ben 描述的 hash_map 向工厂注册您的类。如果你有visual studio,看看DECLARE_DYNCREATE是如何在MFC中实现的。我做了类似的事情来实现一个类工厂。肯定是非标准的,但由于 C++ 不为这种类型的机制提供任何类型的支持,因此任何解决方案都可能是非标准的。

        编辑

        我之前在评论中说过,我正在努力记录我所做事情的缩小版本。缩小版仍然相当大,所以I posted it here。如果有足够的兴趣,我可以在这个网站上复制/粘贴它。告诉我。

        【讨论】:

          【解决方案6】:

          这是一种可怕的、可怕的做法的骨架:

          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>()
          

          然后为Base 的每个相关派生类调用REGISTER(classname);,并使用factories["classname"]-&gt;make() 获取classname 类型的新对象。上述代码的明显缺陷包括内存泄漏的巨大可能性,以及组合宏和模板的一般糟糕之处。

          【讨论】:

            【解决方案7】:

            看看强大的 Boost。

            要使用我的解决方案,您必须做的一件事是向您的所有类添加一个新成员,即包含类名称的static const string。可能还有其他方法可以做到这一点,但这就是我现在所拥有的。

            #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;
            }
            

            【讨论】:

            • 编译器可以处理嵌套 100 深的模板吗?
            • @Emile:通常是的,但预计会很慢。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-07-30
            • 1970-01-01
            • 2011-04-09
            • 2011-04-14
            相关资源
            最近更新 更多