gpt4 book ai didi

c++ - 跨二进制模块实现单例

转载 作者:太空狗 更新时间:2023-10-29 21:39:29 28 4
gpt4 key购买 nike

背景

首先,我认为这个问题超出了C++标准。该标准涉及多个翻译单元(实例化单元),因此涉及多个对象模块,但似乎并没有承认具有多个独立编译和链接的二进制模块(即Linux上的.so文件和Windows上的.dll文件)的可能性。毕竟,后者更多地进入了标准留给当前要考虑的application binary interface (ABI)领域。

当仅涉及单个二进制模块时,以下代码段说明了一种用于制作单例的优雅且便携式(符合标准)的解决方案。

inline T& get() {
static T var{};
return var;
}

关于此解决方案,需要注意两点。首先, inline说明符使函数成为要包含在多个翻译单元中的候选函数,这非常方便。请注意,该标准保证最终二进制模块中只有 get()实例和局部静态变量 var实例(请参阅 here)。

要注意的第二件事是,从C++ 11开始,静态局部变量的初始化已正确同步(请参见 静态局部变量部分 here)。因此,并发调用 get()很好。

现在,我尝试将此解决方案扩展到涉及多个二进制模块的情况。我发现以下变体适用于Windows上的VC++。
// dllexport is used in building the library module, and
// dllimport is used in using the library in an application module.
// Usually controlled by a macro switch.
__declspec(dllexport/dllimport) inline T& get() {
static T var{};
return var;
}

对于非Windows用户请注意: __declspec(dllexport)指定在此模块中实现(定义)实体(即函数,类或对象),并由其他模块引用。另一方面, __declspec(dllimport)指定一个实体未在此模块中实现,而将在其他某个模块中找到。

由于VC++支持导出和导入模板实例化(请参见 here),因此甚至可以对上述解决方案进行模板化。例如:
template <typename T> inline
T& get() {
static T var{};
return var;
}

// EXTERN is defined to be empty in building the library module, and
// to `extern` in using the library module in an application module.
// Again, this is usually controlled by a macro switch.
EXTERN template __declspec(dllexport/dllimport) int& get<int>();

附带说明一下,此处的 inline说明符不是必需的。参见 this S.O.问题。

问题

由于在GCC和clang中没有 __declspec(dllexport/import)等效项,有没有办法使上述解决方案的变体适用于这两个编译器?

另外,在Boost.Log中,我注意到 BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT宏(请参见 全局记录器对象部分 here)。即使应用程序包含多个模块,也可以创建单例。如果有人知道此宏的内部工作原理,欢迎在此处进行解释。

最后,如果您知道制作单例的更好解决方案,请随时发布作为答案。

最佳答案

Since there is no __declspec(dllexport/import) equivalents in GCC and clang, is there a way to make a variant of the above solution that works on these two compilers?



首先,这与编译器无关,而与底层操作系统有关。 GCC(据称是clang)在Windows上确实支持 __declspec(dllexport/import),并且基本上与MSVC对以这种方式标记的功能和对象所做的相同。基本上,标记的符号放置在从dll导出的符号表中(导出表)。例如,当您在运行时查询dll中的符号时,可以使用此表(请参阅 GetProcAddress)。

与DLL一起出现的是一个关联的lib文件,其中包含用于将您的应用程序与dll链接的辅助数据。当您将应用程序与库链接时,链接器使用lib文件来解析对dll符号的引用,并在应用程序二进制文件中构成导入表。当应用程序启动时,操作系统(或更确切地说是操作系统的运行时加载器组件)使用导入表来查找应用程序所依赖的dll,以及从这些dll导入的符号。然后,它使用dll中的导出表来解析dll中引用符号的地址,并完成链接过程。

此过程的重要副作用是,只能动态解析导入的符号,并且您动态链接到的每个符号都与特定的dll相关联。您可以在多个dll和应用程序本身中具有相同名称的符号,并且这些符号只要不导出就将引用不同的实体。如果将它们导出,则链接过程将由于歧义而失败。这使得在Windows上难以实现进程范围内的单例。这也违反了某些C/C++语言规则,因为通过外部链接获取对象或函数的地址(以语言术语)可能会在程序的不同部分产生不同的地址。另一方面,dll更加独立,并且在较小程度上依赖于加载上下文。

在Linux和其他类似POSIX的操作系统上,情况大不相同。链接后,对于每个共享库(可以是so库或应用程序可执行文件),将编译符号表。它列出了此共享库实现的符号和缺少的符号。另外,链接器可以将其他共享对象列表(可选地,带有搜索路径)嵌入到共享对象中,这些列表可用于解析丢失的符号。运行时加载器包括一个链接器,该链接器顺序加载共享对象并构造一个包含所有共享对象中的符号的符号全局表。构造该表后,来自多个共享库的重复符号将解析为一个实现(由于所有实现都被视为等效,因此使用实现列表的负载列表中的第一个共享库)。在加载链接顺序中的后续共享对象时,还将解析所有丢失的符号。

此过程的效果是,即使多个共享库实现了共享链接,每个具有外部链接的符号也将解析为共享对象之一中的单个实现。这更符合C/C++语言规则,并使实现过程范围内的单例变得更简单。一个简单的函数局部静态变量,而不用任何特殊方式标记就足够了。

现在,有多种方法可以影响链接过程,尤其是有一些方法可以限制从共享库中导出的符号。最常用的方法是使用 symbol visibilitylinker scripts。使用这些工具,可以实现非常接近Windows的链接行为,并且具有所有优点和缺点。请注意,在限制符号可见性时,您必须使用 visibility attributepragma标记要从共享库中导出的符号。但是,无需标记要导入的符号。

Also, in Boost.Log, I noticed the BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT macro (see the Global logger objects section here). It is claimed to create singletons even if the application consists of multiple modules. If someone knows about the inner workings of this macro, explanations are welcome here.



在多模块应用程序中使用Boost.Log时,需要将其构建为共享库。这样就可以在整个应用程序中声明对全局记录器的引用的整个过程范围的存储(该存储在Boost.Log dll/so中实现)。当您获得使用 BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT或类似宏声明的记录器时,首先会在存储中查找对记录器的引用。如果未找到记录器,则会创建记录器,并将对它的引用存储回内部存储器中。否则,将使用现有引用。与引用缓存一起,这提供了非常接近函数局部静态变量的性能。

Finally, if you know about any better solutions for making singletons, feel free to post it as an answer.



尽管这并不是真正的答案,但通常应避免单例。它们很难正确实现且不会影响性能。如果确实需要实现一个,那么类似于Boost.Log的解决方案看起来就足够通用了。但是请注意,使用这种解决方案通常不知道哪个模块创建了(因此也称为“拥有”)单例,因此您无法动态卸载任何模块。可能有特定于案例的更简单方法,例如导出返回对本地静态对象的引用的函数。如果您希望具有可移植性并默认情况下支持非默认符号可见性,请始终明确导出符号。

关于c++ - 跨二进制模块实现单例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32883872/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com