想象我有一堆的C ++相关的类(所有延伸相同的基类,并提供相同的构造),我在一个共同的头文件中声明(我包括),以及它们在一些其他文件的实现(我编译和链接静态我的计划构建的一部分)。
我想能够实例化其中的一个路过的名字,这是将被传递到我的节目(无论是作为命令行或编译宏)的参数。
唯一可能的解决方案我看到的是使用宏:
#ifndef CLASS_NAME
#define CLASS_NAME MyDefaultClassToUse
#endif
BaseClass* o = new CLASS_NAME(param1, param2, ..);
它是唯一有价值的方法?
这是使用了常用解决的问题登记模式 :
这是注册表模式描述的情况:
对象需要联系另一个对象,只知道对象的名称或它提供的服务的名称,但不知道如何与我们联系。 提供需要的对象,服务或角色的名称,并返回一个封装中如何联系命名对象的知识远程代理服务。
这是形成一个面向服务的架构(SOA),并在OSGi的服务层的基础上基本相同的发布/查找模式。
您可以实现一个注册表通常使用一个单独的对象,单身的对象是在编译时或在启动时对象的名称,并构建它们的方式告知。 然后,你可以使用它来创建对需求的对象。
例如:
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);
}
你注册的对象和创造功能,像这样的名字:
Registry<I> registry;
registry.register("MyClass", &MyClass::Creator);
std::auto_ptr<T> myT(registry.create("MyClass"));
那么我们可能会用聪明的宏简化这使它能够在编译时完成。 ATL使用注册表模式的,可以在运行时通过名字来创建组件类-注册是使用类似下面的代码一样简单:
OBJECT_ENTRY_AUTO(someClassID, SomeClassName);
这个宏被放置在头文件中的某个地方,魔术使其与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.
为什么不使用对象工厂?
在最简单的形式:
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,而不是玻璃陶瓷:
typedef DefaultClass MyDefaultClassToUse;
这相当于,避免了宏(宏坏;-))。
如果决定在运行时进行,你需要编写自己的代码来支持它。 该simples解决方案是测试串并实例相应类的功能。
这方面的一个扩展版本(允许独立代码段登记他们的班)将是一个map<name, factory function pointer>
。
你提到的两种可能性 - 命令行和编译宏,但对于每一个都是完全不同的解决方案。
如果选择的是由编译宏比它可与#define和的#ifdefs等来解决一个简单的问题。 你提出的解决方案是不比任何人差。
但是,如果选择在运行时使用命令行参数做,那么你需要有一些厂的框架,能够接收一个字符串,并创建相应的对象。 这可以用一个简单的,静态来完成if().. else if()... else if()...
拥有一切准备或链可以是完全动态的框架,对象登记和被克隆提供自己的新实例。
虽然现在的问题存在了四年多就仍然是有用的。 由于呼唤新的代码未知在编译和链接主代码文件的时刻是在这些日子里一个非常常见的场景。 一个解决这个问题根本没有被提及。 因此,我想给观众指向不同类型的解决方案不是建立在C ++中。 C ++本身没有能力表现得像Class.forName()
从Java或类似的已知的Activator.CreateInstance(type)
从.NET已知的。 由于上述原因,没有监督一个虚拟机JIT代码在运行。 但无论如何, LLVM ,低级别的虚拟机,为您提供了所需的工具和库来读入编译库。 基本上,你需要执行两个步骤:
- 编译C / C ++源代码,你喜欢动态实例。 您需要编译它位码,让您在结束了,让我们说,foo.bc. 你可以用铿锵做到这一点,并提供一个编译器开关:
clang -emit-llvm -o foo.bc -c foo.c
- 你需要再使用
ParseIRFile()
从法llvm/IRReader/IRReader.h
解析foo.bc
文件,以获得相关的功能(LLVM本身只知道用作位码是CPU操作码的直接抽象和相当unsimiliar到更高层的中间表示像Java字节码)。 例如参考此文章更完整的代码说明。
设置这些步骤勾勒上面可以从C ++其它现有未知的函数和方法也动态地调用之后。
在过去,我已经实现了这样的类可以在运行时自行注册而本身不必知道具体是对他们工厂的工厂模式。 的关键是使用所谓的(这个)“由初始化连接”非标准的编译器的功能,其特征在于,你对每个类的实现文件(例如,布尔)声明的伪静态变量,以及与所述注册的呼叫初始化它常规。
在这个方案中,每个类都有#包括含出厂头,但厂方知道什么,除了接口类。 你可以从字面上添加或从您的构建中移除实现类,并没有更改代码重新编译。
美中不足的是,只有一些编译器通过初始化支持附件 - IIRC别人初始化在第一次使用的文件范围变量(方法同功能的本地静态工作),这是没有帮助在这里,因为哑变量从未被访问,工厂地图会总是可以找到空。
我感兴趣的(MSVC和GCC)编译器都支持这一点,虽然,所以它不是我的问题。 你必须自己决定该解决方案是否适合你。