2

现在,我实现了一个工厂类来动态创建带有标识字符串的类,请看以下代码:

void IOFactory::registerIO()
{
    Register("NDAM9020", []() -> IOBase * {
        return new NDAM9020();
    });

    Register("BK5120", []() -> IOBase * {
        return new BK5120();
    });
}

std::unique_ptr<IOBase> IOFactory::createIO(std::string ioDeviceName)
{
    std::unique_ptr<IOBase> io = createObject(ioDeviceName);
    return io;
}

所以我们可以用注册的名字创建IO类:

IOFactory ioFactory;
auto io = ioFactory.createIO("BK5120");

这种方法的问题是,如果我们添加另一个 IO 组件,我们应该在 registerIO 函数中添加另一个寄存器代码,然后重新编译整个项目。所以我想知道我是否可以在运行时从配置文件(见下文)动态注册类。

io_factory.conf
------------------
NDAM9020:NDAM9020
BK5120:BK5120
------------------

第一个是标识名,第二个是类名。

我尝试过使用宏,但宏中的参数不能是字符串。所以我想知道是否还有其他方法。感谢提前。


更新:

没想到这么多评论和回答,谢谢大家,抱歉回复晚了。

我们当前的操作系统是 Ubuntu16.04,我们使用内置编译器 gcc/g++5.4.0,我们使用 CMake 来管理构建。

而且我应该提一下,我应该在运行时注册类不是必须的,如果有办法在编译期间这样做也是可以的。我想要的只是在我想注册另一个类时避免重新编译。

4

4 回答 4

3

所以我想知道我是否可以在运行时从配置文件(见下文)动态注册类。

不。从 C++20 开始,C++ 没有允许它的反射特性。但是您可以在编译时通过从配置文件生成一个简单的 C++ 实现文件来实现。

于 2020-06-08T09:27:55.240 回答
2

如何在运行时使用 C++ 在工厂类中动态注册类

阅读有关 C++ 的更多信息,至少是一本好的C++ 编程书籍,并查看一个好的 C++ 参考网站,以及后来的n3337,即 C++11 标准。还请阅读您的 C++ 编译器(可能是GCCClang)的文档,如果您有,请阅读您的操作系统的文档。如果您的操作系统中可以使用插件,可以在运行时注册一个工厂函数(通过在加载提供它的插件之后引用该函数)。例如,Mozilla firefox浏览器或最近的GCC编译器(例如GCC 10启用插件)或鱼壳正在执行此操作。

所以我想知道我是否可以在运行时从配置文件(见下文)动态注册类。

大多数 C++ 程序都在操作系统下运行,例如 Linux。一些操作系统提供插件机制。对于 Linux,请参阅dlopen(3)dlsym(3)dlclose(3)dladdr(3)C++ dlopen mini-howto。对于 Windows,请深入了解其文档

因此,使用最近的 C++ 实现和一些最近的操作系统,您可以在运行时注册工厂类(使用插件),并且可以找到库(例如QtPOCO)来帮助您。

但是,在纯标准 C++ 中,翻译单元集是静态已知的,并且不存在插件。因此,给定程序中的函数、lambda 表达式或类的集合是有限的,并且不会随时间变化。

在纯 C++ 中,有效函数指针集或给定 变量的有效可能值集std::function是有限的。其他任何事情都是未定义的行为。在实践中,许多现实生活中的 C++ 程序通过其操作系统或 JIT 编译库接受插件。

您当然可以考虑使用JIT 编译库,例如asmjitlibgccjitLLVM它们是特定于实现的,因此您的代码将不可移植。

在 Linux 上,许多QtGTKmm应用程序(例如KDE和大多数 Web 浏览器,例如KonquerorChrome或 Firefox)都是用 C++ 编码的,并使用工厂函数加载插件。检查strace(1)ltrace(1)

传闻微软的Trident Web 浏览器是用 C++ 编码的,并且可能接受插件。

我试过使用宏,但宏中的参数不能是字符串。

宏参数可以被字符串化。你可以玩x-macros技巧。

我想要的只是在我想注册另一个类时避免重新编译。

在 Ubuntu 上,我建议在您的程序或库中接受插件

dlopen(3)与绝对文件路径一起使用;该插件通常会作为程序选项(如RefPerSysGCC 那样)传递,并dlopen在程序或库初始化时 -ed。实际上,您可以拥有很多插件(数十万个,请参阅manydl.c并使用pmap(1)proc(5)进行检查)。插件中的dlsym(3) -ed C++ 函数声明extern "C" 为禁用名称修改

单个 C++ 文件插件 (in yourplugin.cc) 可以使用 g++ -Wall -O -g -fPIC -shared yourplugin.cc -o yourplugin.so和以后编译dlopen "./yourplugin.so"或绝对路径 (或适当地配置您的$LD_LIBRARY_PATH-see ld.so(8) - 并传递"yourplugin.so"dlopen)。还要注意Rpath

还可以考虑使用libgccjit (至少在将您的 GCC 升级到GCC 9之后,可能通过从其源代码编译它)(它比在某些文件中生成临时 C++ 代码并将该文件编译到临时插件中更快)。

为了便于调试加载的插件,您可能会对 Ian Taylor 的libbacktrace 感兴趣

请注意,您的程序的全局符号(声明为extern "C")可以通过将nullptr 文件路径传递给dlopen(3)来按名称访问,然后在获得的句柄上使用dlsym(3) 。你想-rdynamic -ldl在链接你的程序(或你的共享库)时通过。

我想要的只是在我想注册另一个类时避免重新编译。

您可能会在不同的翻译单元中注册课程(大概是一个简短的翻译单元)。您可以从RefPerSys 文件的多个#include-s中获得灵感generated/rps-name.hh。然后,您只需重新编译单个*.cc文件并重新链接整个程序或库。请注意,Qtmoc的 .

另请阅读 J.Pitrat 关于人工存在的书:有意识的机器的良心ISBN,它解释了为什么元编程方法很有用。研究GCC(或RefPerSys)的源代码,在相关时使用或从SWIGANTLRGNU bison(它们都生成 C++ 代码)中获取灵感

于 2020-06-08T11:57:29.210 回答
1

您似乎要求的活力比实际需要的更多。您希望避免工厂本身必须了解其中注册的所有类。

好吧,这是可行的,而无需一直进行运行时代码生成!

这种工厂有几种实现方式;但我显然偏向于我自己的:einpoklum 的工厂类(gist.github.com)

简单的使用示例:

#include "Factory.h"
// we now have:
//
// template<typename Key, typename BaseClass, typename... ConstructionArgs>
// class Factory;
//
#include <string>

struct Foo { Foo(int x) { }; }
struct Bar : Foo { Bar(int x) : Foo(x) { }; }

int main()
{
    util::Factory<std::string, Foo, int> factory;
    factory.registerClass<Bar>("key_for_bar");
    auto* my_bar_ptr factory.produce("key_for_bar");
}

笔记:

  • std::string用作键;如果您愿意,您可以使用数值作为键的工厂。
  • 所有注册的类必须是BaseClass为工厂选择的值的子类。我相信您可以更改工厂以避免这种情况,但是您将始终从中受益void *
  • 您可以将其包装在一个单例模板中,以获得一个可以在任何地方使用的单一、全局、静态初始化安全的工厂。

现在,如果您动态加载一些插件(请参阅@BasileStarynkevitch 的答案),您只需要该插件公开一个初始化函数,该函数registerClass()在工厂上进行类调用;并在加载插件后立即调用此初始化函数。或者,如果您有一个静态初始化安全的单例工厂,您可以在插件共享库的静态块中进行注册调用- 但要小心,我不是共享库加载方面的专家。

于 2020-06-09T09:56:53.477 回答
0

绝对的!

有一个 2006 年的旧古董帖子解决了我多年的生活。该实现围绕着一个集中式注册中心运行,该注册中心具有一个使用 REGISTER_X 宏扩展的分散式注册方法,请查看:

https://web.archive.org/web/20100618122920/http://meat.net/2006/03/cpp-runtime-class-registration/

不得不承认@einpoklum 工厂看起来也很棒。我创建了一个 headeronly 示例 gist,其中包含代码和示例:

https://gist.github.com/h3r/5aa48ba37c374f03af25b9e5e0346a86

于 2022-01-19T17:58:48.940 回答