【发布时间】:2015-10-01 09:20:39
【问题描述】:
背景
首先,我认为这个问题超出了 C++ 标准。该标准处理多个翻译单元(实例化单元)和多个对象模块,但似乎不承认有多个独立编译和链接的二进制模块的可能性(即,Linux 上的.so 文件和 Windows 上的.dll 文件)。毕竟,后者更多地进入了application binary interface (ABI) 的世界,该标准目前留给实现来考虑。
当只涉及单个二进制模块时,以下代码 sn-p 说明了一个优雅且可移植(符合标准)的单例解决方案。
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)。据称,即使应用程序由多个模块组成,它也会创建单例。如果有人知道这个宏的内部工作原理,欢迎在这里解释。
最后,如果您知道任何制作单例的更好解决方案,请随时将其发布为答案。
【问题讨论】:
-
您能否为非 Windows 用户解释
dllimport/dllexport的确切作用以及使用多极 .so 文件时为什么会出现问题?理想情况下,给出一个简单的测试代码。 -
@Lingxi 好吧,为了设计,我会问自己是否需要单例来实现你想要的。
-
@πάνταῥεῖ 事实上,我只是想要一种方便的方法来创建和共享多个二进制模块的实例,就像
BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT所做的那样。 -
@灵曦嗯。
dllimport与简单的extern有何不同? -
@Walter 也许,它会阻止模块从函数体中自己生成定义,而是在另一个模块中查找定义。
标签: c++ c++11 singleton dynamic-linking boost-log