从名称实例化类?

想象我有一堆C ++相关的类(所有扩展相同的基类和提供相同的构造函数),我在一个共同的头文件(我包括),以及他们的一些其他文件(我编译和链接静态作为我的程序的一部分)。

我想能够实例化其中的一个传递名称,这是一个参数,必须传递给我的程序(作为命令行或作为编译macros)。

我看到的唯一可能的解决scheme是使用macros:

#ifndef CLASS_NAME #define CLASS_NAME MyDefaultClassToUse #endif BaseClass* o = new CLASS_NAME(param1, param2, ..); 

这是唯一有价值的方法吗?

这是一个通常使用registry模式解决的问题:

这是registry模式描述的情况:

对象需要联系另一个对象,只知道该对象的名称或提供的服务的名称,而不知道如何联系它。 提供一个采用对象,服务或angular色名称的服务,并返回一个远程代理,该代理封装了如何联系指定对象的知识。

它是构成面向服务的体系结构(SOA)和OSGi中的服务层的基础的基本发布/查找模型。

您通常使用单例对象实现registry,在编译时或启动时通知单例对象的对象的名称,以及构build它们的方式。 然后,您可以使用它来按需创build对象。

例如:

 template<class T> class Registry { typedef boost::function0<T *> Creator; typedef std::map<std::string, Creator> Creators; Creators _creators; public: void register(const std::string &className, const Creator &creator); T *create(const std::string &className); } 

您注册对象的名称和创build函数如下所示:

 Registry<I> registry; registry.register("MyClass", &MyClass::Creator); std::auto_ptr<T> myT(registry.create("MyClass")); 

然后,我们可以用巧妙的macros来简化它,使它能够在编译时完成。 ATL使用可以在运行时按名称创build的CoClasses注册模式 – 注册就像使用下面的代码一样简单:

 OBJECT_ENTRY_AUTO(someClassID, SomeClassName); 

这个macros被放置在你的头文件的某个地方,魔术导致它在COM服务器启动时被注册到单例中。

实现这一点的一种方式是对从类“名称”到工厂函数的映射进行硬编码。 模板可能会缩短代码。 STL可以使编码更容易。

 #include "BaseObject.h" #include "CommonClasses.h" template< typename T > BaseObject* fCreate( int param1, bool param2 ) { return new T( param1, param2 ); } typedef BaseObject* (*tConstructor)( int param1, bool param2 ); struct Mapping { string classname; tConstructor constructor; pair<string,tConstructor> makepair()const { return make_pair( classname, constructor ); } } mapping[] = { { "class1", &fCreate<Class1> } , { "class2", &fCreate<Class2> } // , ... }; map< string, constructor > constructors; transform( mapping, mapping+_countof(mapping), inserter( constructors, constructors.begin() ), mem_fun_ref( &Mapping::makepair ) ); 

编辑 – 根据一般的要求:)稍微返工,让事情看起来更顺畅(信贷石免费谁可能不想自己添加一个答案)

 typedef BaseObject* (*tConstructor)( int param1, bool param2 ); struct Mapping { string classname; tConstructor constructor; operator pair<string,tConstructor> () const { return make_pair( classname, constructor ); } } mapping[] = { { "class1", &fCreate<Class1> } , { "class2", &fCreate<Class2> } // , ... }; static const map< string, constructor > constructors( begin(mapping), end(mapping) ); // added a flavor of C++0x, too. 

为什么不使用对象工厂?

最简单的forms是:

 BaseClass* myFactory(std::string const& classname, params...) { if(classname == "Class1"){ return new Class1(params...); }else if(...){ return new ...; }else{ //Throw or return null } return NULL; } 

在C ++中,这个决定必须在编译时做出。

在编译期间,你可以使用typedef而不是macor:

 typedef DefaultClass MyDefaultClassToUse; 

这是等同的,并避免macros(macros不好;-))。

如果要在运行时作出决定,则需要编写自己的代码来支持它。 simples解决scheme是一个函数,它testingstring并实例化相应的类。

它的扩展版本(允许独立的代码段注册它们的类)将是一个map<name, factory function pointer>

你提到了两种可能性 – 命令行和汇编macros,但是对于每一个的解决scheme是非常不同的。

如果这个select是由一个编译macros而不是一个简单的问题,可以用#defines和#ifdefs等来解决。 您提出的解决scheme与任何解决scheme一样好。

但是如果在运行时使用命令行参数进行select,则需要有一些能够接收string并创build相应对象的Factory框架。 这可以通过一个简单的,静态的if().. else if()... else if()...链来完成,它有所有的可能性,或者可以是一个完全dynamic的框架,对象注册并被克隆来提供自己的新实例。

虽然现在这个问题已经存在了四年多了,但它仍然有用。 因为在编译和链接主代码文件的时候调用未知的新代码是当今非常常见的情况。 这个问题的一个解决scheme根本没有提到。 因此,我喜欢将观众指向不是以C ++构build的另一种解决scheme。 C ++本身没有像从Java中知道的Class.forName()那样行为的能力,或者像.NET中的Activator.CreateInstance(type)一样。 由于上述原因,虚拟机不能即时监控JIT代码。 但无论如何, LLVM (低级别虚拟机)为您提供所需的工具和库来读入编译的库。 基本上,你需要执行两个步骤:

  1. 编译您想要dynamic实例化的C / C ++源代码。 你需要把它编译成bitcode,所以你最终在一个,比如foo.bc。 你可以用clang来做,并提供一个编译器开关: clang -emit-llvm -o foo.bc -c foo.c
  2. 然后,您需要使用来自llvm/IRReader/IRReader.hParseIRFile()方法来parsingfoo.bc文件以获取相关函数(LLVM本身只知道函数,因为位代码是CPU操作码的直接抽象,对于相当不熟悉更高级的中间表示,如Java字节码)。 请参阅本文的实例以获取更完整的代码描述。

在上面设置完这些步骤之后,您还可以从C ++dynamic调用其他未知的函数和方法。

在过去,我已经实现了Factory模式,使得类可以在运行时自行注册,而工厂本身不必具体了解它们。 关键是使用称为(IIRC)的“初始化附件”的非标准编译器function,其中您在实现文件中为每个类(例如bool)声明一个虚拟静态variables,并通过调用注册来初始化它常规。

在这个scheme中,每个类都必须#include包含它的工厂的头文件,但工厂除了接口类之外什么都不知道。 你可以从你的版本中实际添加或者删除实现类,并且不需要修改代码就可以重新编译。

值得注意的是,只有一些编译器通过初始化来支持附件 – IIRC其他人在第一次使用时初始化文件范围variables(与本地静态函数相同的方式),这在这里没有帮助,因为虚拟variables永远不会被访问,工厂映射将会总是被发现空的。

我感兴趣的编译器(MSVC和GCC)确实支持这个,所以对我来说不是问题。 你必须自己决定这个解决scheme是否适合你。