从名称实例化类?

时间:2009-07-08 08:00:25

标签: c++ class macros

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

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

我看到唯一可行的解​​决方案是使用宏:

#ifndef CLASS_NAME
#define CLASS_NAME MyDefaultClassToUse
#endif

BaseClass* o = new CLASS_NAME(param1, param2, ..);

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

7 个答案:

答案 0 :(得分:39)

这是一个通常使用Registry Pattern

解决的问题
  

这就是这种情况   Registry Pattern描述:

     
    

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

  
     

它与基本的发布/查找模型相同   这构成了服务的基础   面向架构(SOA)和for   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使用CoClasses的注册表模式,可以在运行时按名称创建 - 注册就像使用类似下面的代码一样简单:

OBJECT_ENTRY_AUTO(someClassID, SomeClassName);

这个宏放在你的头文件中,magic启动它在COM服务器启动时注册单例。

答案 1 :(得分:10)

实现这一点的一种方法是对从“名称”类到工厂函数的映射进行硬编码。模板可以缩短代码。 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 ) );

编辑 - 根据一般要求:)进行一些修改以使事情看起来更顺畅(对Stone Free的信任,他可能不想自己添加答案)

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.

答案 2 :(得分:5)

为什么不使用对象工厂?

最简单的形式:

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

答案 3 :(得分:2)

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

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

typedef DefaultClass MyDefaultClassToUse;

这是等效的,并避免宏(宏坏; - ))。

如果要在运行时做出决定,您需要编写自己的代码来支持它。 simples解决方案是一个测试字符串并实例化相应类的函数。

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

答案 4 :(得分:1)

你提到了两种可能性 - 命令行和编译宏,但每种方案的解决方案都大相径庭。

如果选择是由编译宏做出的,那么这是一个简单的问题,可以通过#defines和#ifdefs等来解决。你提出的解决方案与任何解决方案一样好。

但是如果使用命令行参数在运行时进行选择,则需要一些能够接收字符串并创建适当对象的Factory框架。这可以使用一个简单的静态if().. else if()... else if()...链来完成,该链具有所有可能性,或者可以是一个完全动态的框架,其中对象注册并被克隆以提供自己的新实例。

答案 5 :(得分:1)

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

  1. 编译您想要动态实例化的C / C ++源代码。你需要把它编译成bitcode,所以你最终会进入foo.bc。您可以使用clang执行此操作并提供编译器开关:clang -emit-llvm -o foo.bc -c foo.c
  2. 然后,您需要使用ParseIRFile()中的llvm/IRReader/IRReader.h方法来解析foo.bc文件以获取相关函数(LLVM本身只知道函数,因为bitcode是CPU的直接抽象操作码并且对Java字节码等更高级的中间表示非常不熟悉。例如,请参阅此article以获取更完整的代码说明。
  3. 在设置上面描述的这些步骤之后,您也可以从C ++动态调用其他先前未知的函数和方法。

答案 6 :(得分:0)

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

在这个方案中,每个类都必须#include包含其工厂的头,但工厂除了接口类之外什么都不知道。您可以从构建中逐字添加或删除实现类,并在不进行代码更改的情况下重新编译。

问题是只有一些编译器通过初始化支持附件 - IIRC其他人在第一次使用时初始化文件范围变量(与函数本地静态工作相同),这在这里没有帮助,因为从未访问虚拟变量并且工厂地图总是空着。

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