从C++中包含类名的字符串中动态创建类的实例

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

本文关键字:创建 实例 动态 字符串 包含类 C++      更新时间:2023-10-16

假设我有一个有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
  ...
};

在运行时,我想读取一个文件,其中包含要创建和馈送的子级。假设我已经阅读了该文件,字符串"名称"的向量包含子类的名称(即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_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++没有提供这样的类实例的动态构造方法。但是,您可以使用代码生成从类列表中生成"暴力"代码(如上文所示)。然后,#include是您的convert_string_to_instance方法中生成的代码。

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

我问了一个题为"对象创建者函数与宏的自动注册"的问题,该宏运行以下示例程序:

#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 constructorn"; } }; }
REGISTER_TYPE(A::DerivedA, "A");
namespace B { struct DerivedB : public Object { DerivedB() { std::cout << "B::DerivedB constructorn"; } }; }
REGISTER_TYPE(B::DerivedB, "Bee");
namespace C { struct DerivedC : public Object { DerivedC() { std::cout << "C::DerivedC constructorn"; } }; }
REGISTER_TYPE(C::DerivedC, "sea");
namespace D { struct DerivedD : public Object { DerivedD() { std::cout << "D::DerivedD constructorn"; } }; }
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

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

您可以使用Greg建议的codegen方法来构建这个工厂表,例如doxygen可以解析您的源代码,并以xml格式输出所有类的列表以及继承关系,这样您就可以很容易地找到从公共"接口"基类派生的所有类。

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

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

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

我的想法类似于用字段替换子类重构,但我更进一步。

您可以滥用预处理器并设置一些静态类成员,这些成员通过本描述的hash_map向工厂注册您的类。如果你有visualstudio,看看DECLARE_DYNCREATE是如何在MFC中实现的。我已经做了一些类似的事情来实现类工厂。当然是非标准的,但由于C++没有为这种类型的机制提供任何形式的支持,任何解决方案都可能是非标准化的。

编辑

我在早些时候的一篇评论中说,我正在记录我所做的事情的缩小版本。缩小后的版本仍然相当大,所以我把它发布在这里。如果有足够的兴趣,我可以把它复制/粘贴到这个网站上。让我知道。

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

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"]->make()来获得类型为classname的新对象。上述代码的明显缺陷包括内存泄漏的巨大可能性,以及将宏和模板组合在一起的普遍错误。

看看强大的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;
}