C++ 从名称实例化类?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1096700/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Instantiate class from name?
提问by puccio
imagine I have a bunch of C++ related classes (all extending the same base class and providing the same constructor) that I declared in a common header file (which I include), and their implementations in some other files (which I compile and link statically as part of the build of my program).
想象一下,我有一堆 C++ 相关类(都扩展了相同的基类并提供了相同的构造函数),我在一个公共头文件(我包含)中声明了它们,以及它们在其他一些文件中的实现(我编译并静态链接)作为我的程序构建的一部分)。
I would like to be able to instantiate one of them passing the name, which is a parameter that has to be passed to my program (either as command line or as a compilation macro).
我希望能够实例化其中一个传递名称,这是一个必须传递给我的程序的参数(作为命令行或作为编译宏)。
The only possible solution I see is to use a macro:
我看到的唯一可能的解决方案是使用宏:
#ifndef CLASS_NAME
#define CLASS_NAME MyDefaultClassToUse
#endif
BaseClass* o = new CLASS_NAME(param1, param2, ..);
Is it the only valuable approach?
这是唯一有价值的方法吗?
回答by 1800 INFORMATION
This is a problem which is commonly solved using the Registry Pattern:
这是一个通常使用注册表模式解决的问题:
This is the situation that the Registry Pattern describes:
Objects need to contact another object, knowing only the object's name or the name of the service it provides, but not how to contact it. Provide a service that takes the name of an object, service or role and returns a remote proxy that encapsulates the knowledge of how to contact the named object.
It's the same basic publish/find model that forms the basis of a Service Oriented Architecture (SOA) and for the services layer in OSGi.
这是注册表模式描述的情况:
对象需要联系另一个对象,只知道对象的名称或它提供的服务的名称,而不知道如何联系它。提供一个服务,该服务采用对象、服务或角色的名称,并返回封装了如何联系命名对象的知识的远程代理。
它与构成面向服务的体系结构 (SOA) 和 OSGi 中的服务层的基础相同的基本发布/查找模型相同。
You implement a registry normally using a singleton object, the singleton object is informed at compile time or at startup time the names of the objects, and the way to construct them. Then you can use it to create the object on demand.
您通常使用单例对象实现注册表,单例对象在编译时或启动时被告知对象的名称,以及构造它们的方式。然后您可以使用它来按需创建对象。
For example:
例如:
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);
}
You register the names of the objects and the creation functions like so:
您可以像这样注册对象的名称和创建函数:
Registry<I> registry;
registry.register("MyClass", &MyClass::Creator);
std::auto_ptr<T> myT(registry.create("MyClass"));
We might then simplify this with clever macros to enable it to be done at compile time. ATLuses the Registry Pattern for CoClasses which can be created at runtime by name - the registration is as simple as using something like the following code:
然后我们可以使用巧妙的宏来简化它,使其能够在编译时完成。ATL使用 CoClass 的注册表模式,它可以在运行时按名称创建 - 注册就像使用以下代码一样简单:
OBJECT_ENTRY_AUTO(someClassID, SomeClassName);
This macro is placed in your header file somewhere, magic causes it to be registered with the singleton at the time the COM server is started.
这个宏被放置在你的头文件中的某个地方,魔法使它在 COM 服务器启动时注册到单例中。
回答by xtofl
A way to implement this is hard-coding a mapping from class 'names' to a factory function. Templates may make the code shorter. The STL may make the coding easier.
实现这一点的一种方法是硬编码从类“名称”到工厂函数的映射。模板可能会使代码更短。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 ) );
EDIT -- upon general request :) a little rework to make things look smoother (credits to Stone Free who didn't probably want to add an answer himself)
编辑 - 根据一般要求:) 稍微修改一下,让事情看起来更顺畅(感谢 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.
回答by mandrake
Why not use an object factory?
为什么不使用对象工厂?
In its simplest form:
以其最简单的形式:
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;
}
回答by peterchen
In C++, this decision must be made at compile time.
在 C++ 中,这个决定必须在编译时做出。
During compile time you could use a typedef rather than a macor:
在编译期间,您可以使用 typedef 而不是 macor:
typedef DefaultClass MyDefaultClassToUse;
this is equivalent and avoids a macro (macros bad ;-)).
这是等效的并且避免了宏(宏坏;-))。
If the decision is to be made during run time, you need to write your own code to support it. The simples solution is a function that tests the string and instantiates the respective class.
如果要在运行时做出决定,则需要编写自己的代码来支持它。最简单的解决方案是一个测试字符串并实例化相应类的函数。
An extended version of that (allowing independent code sections to register their classes) would be a map<name, factory function pointer>
.
其扩展版本(允许独立代码部分注册它们的类)将是map<name, factory function pointer>
.
回答by shoosh
You mention two possibilities - Command line and compilation macro but the solution for the each one is vastly different.
您提到了两种可能性 - 命令行和编译宏,但每一种的解决方案都大不相同。
If the choice is made by a compilation macro than it's a simple problem which can be solved with #defines and #ifdefs and the like. The solution you propose is as good as any.
如果选择是由编译宏做出的,那么这是一个简单的问题,可以使用 #defines 和 #ifdefs 等来解决。您提出的解决方案与任何解决方案一样好。
But if the choice is made in run-time using a command line argument then you need to have some Factory framework that is able to receive a string and create the appropriate object. This can be done using a simple, static if().. else if()... else if()...
chain that has all the possibilities or can be a fully dynamic framework where objects register and are being cloned to provide new instances of themselves.
但是如果选择是在运行时使用命令行参数做出的,那么您需要有一些能够接收字符串并创建适当对象的 Factory 框架。这可以使用一个简单的静态if().. else if()... else if()...
链来完成,该链具有所有可能性,或者可以是一个完全动态的框架,其中对象注册并被克隆以提供它们自己的新实例。
回答by repl
Though the question exist now for more than four years it is still useful. Because calling for new code unknown at the moment of compiling and linking the main code files is in these days a very common scenario. One solution to this question isn't mentioned at all. Thus, I like to point the audience to a different kind of solution not built in C++. C++ itself has no capability to behave like Class.forName()
known from Java or like Activator.CreateInstance(type)
known from .NET. Due to mentioned reasons, that there is no supervision by a VM to JIT code on the fly. But anyhow, LLVM, the low level virtual machine, gives you the needed tools and libs to read-in a compiled lib. Basically, you need to execute two steps:
尽管这个问题已经存在四年多了,但它仍然很有用。因为在编译和链接主代码文件时调用未知的新代码在当今是一种非常普遍的情况。根本没有提到这个问题的一种解决方案。因此,我喜欢向观众指出另一种不是用 C++ 构建的解决方案。C++ 本身没有能力表现得像Class.forName()
Java 中Activator.CreateInstance(type)
已知的或.NET 中已知的那样。由于上述原因,VM 没有即时监督 JIT 代码。但无论如何,低级虚拟机LLVM为您提供了读入已编译库所需的工具和库。基本上,您需要执行两个步骤:
- compile your C/C++ source code, which you like to instantiate dynamically. You need to compile it to bitcode, so you end up in a, let say, foo.bc. You can do it with clang and provide a compiler switch:
clang -emit-llvm -o foo.bc -c foo.c
- You need then to use the
ParseIRFile()
method fromllvm/IRReader/IRReader.h
to parse thefoo.bc
file to get the relevant functions (LLVM itself only knows functions as the bitcode is a direct abstraction of CPU opcodes and quite unsimiliar to more high-level intermediate representations like the Java bytecode). Refer for instance to this articlefor a more complete code description.
- 编译您喜欢动态实例化的 C/C++ 源代码。您需要将其编译为位码,因此您最终会得到一个,比如说,foo.bc。您可以使用 clang 并提供编译器开关:
clang -emit-llvm -o foo.bc -c foo.c
- 然后,您需要使用
ParseIRFile()
from 方法llvm/IRReader/IRReader.h
解析foo.bc
文件以获取相关函数(LLVM 本身只知道函数,因为位码是 CPU 操作码的直接抽象,与 Java 字节码等更高级的中间表示非常不同)。例如,请参阅本文以获得更完整的代码说明。
After setting up these steps sketched above you can call dynamically also from C++ other prior unknown functions and methods.
设置上面勾画的这些步骤后,您还可以从 C++ 动态调用其他先前未知的函数和方法。
回答by repl
In the past, I've implemented the Factory pattern in such a way that classes can self-register at runtime without the factory itself having to know specifically about them. The key is to use a non-standard compiler feature called (IIRC) "attachment by initialisation", wherein you declare a dummy static variable in the implementation file for each class (e.g. a bool), and initialise it with a call to the registration routine.
过去,我以这样一种方式实现了工厂模式,即类可以在运行时自行注册,而工厂本身不必特别了解它们。关键是使用称为(IIRC)“通过初始化附加”的非标准编译器功能,其中您在每个类(例如bool)的实现文件中声明一个虚拟静态变量,并通过调用注册来初始化它常规。
In this scheme, each class has to #include the header containing its factory, but the factory knows about nothing except the interface class. You can literally add or remove implementation classes from your build, and recompile with no code changes.
在这个方案中,每个类都必须#include 包含其工厂的标头,但工厂除了接口类之外一无所知。您可以在构建中添加或删除实现类,并且无需更改代码即可重新编译。
The catch is that only some compilers support attachment by initialisation - IIRC others initialise file-scope variables on first use (the same way function-local statics work), which is no help here since the dummy variable is never accessed and the factory map will always be found empty.
问题是只有一些编译器通过初始化支持附件 - IIRC 其他编译器在第一次使用时初始化文件范围变量(与函数本地静态工作的方式相同),这在这里没有帮助,因为虚拟变量永远不会被访问,工厂映射将总是被发现是空的。
The compilers I'm interested in (MSVC and GCC) do support this, though, so it's not a problem for me. You'll have to decide for yourself whether this solution suits you.
不过,我感兴趣的编译器(MSVC 和 GCC)确实支持这一点,所以这对我来说不是问题。您必须自己决定此解决方案是否适合您。