CMake编译下如何实现真正静态变量?解决类重复注册问题
问题描述
现有如下宏定义:
#define REGISTER_CLASS(CLASSNAME) struct CLASSNAME; \ static bool CLASSNAME##registered = \ (\ (Register<CLASSNAME>()), \ true \ );
在类定义前使用该宏时,类可通过Register函数完成注册。但存在继承场景时,例如:
// 文件1 REGISTER_CLASS(Foo) class Foo { // ... }; // 文件2 REGISTER_CLASS(FooBar1) class FooBar1: public Foo { // ... }; // 文件3 REGISTER_CLASS(FooBar2) class FooBar2: public Foo { // ... };
尽管Fooregistered是静态变量,但在CMake编译环境下,Foo类仍会被多次注册。而不使用CMake编译时,代码行为正常。需要解决CMake环境下的重复注册问题,实现真正的单次静态变量初始化。
解决方法
方案1:使用C++17 inline静态变量
修改宏定义,将静态变量声明为inline,C++17标准规定多个编译单元中的同名inline静态变量会被合并为单一实例,确保Register<Foo>()仅执行一次:
#define REGISTER_CLASS(CLASSNAME) struct CLASSNAME; \ inline static bool CLASSNAME##registered = \ (\ (Register<CLASSNAME>()), \ true \ );
这是最简洁且符合现代C标准的解决方案,前提是项目支持C17及以上版本。
方案2:利用模板静态成员的单例特性
模板的静态成员在整个程序生命周期中只会实例化一次,可将注册逻辑封装到模板中:
// 定义通用注册模板 template<typename T> class Registrar { public: static const bool registered; }; template<typename T> const bool Registrar<T>::registered = (Register<T>(), true); // 修改宏定义 #define REGISTER_CLASS(CLASSNAME) struct CLASSNAME; \ static const auto& CLASSNAME##registrar = Registrar<CLASSNAME>::registered;
无论多少个编译单元引用Registrar<Foo>::registered,该静态成员只会初始化一次,从根源避免重复注册。此方案兼容C++11及以上版本。
方案3:在Register函数内部做去重校验
如果无法修改宏的结构,可在Register函数中添加全局的注册记录,检查当前类是否已经注册过:
#include <unordered_set> #include <typeinfo> std::unordered_set<const std::type_info*> registered_classes; template<typename T> void Register() { const auto& type = typeid(T); if (registered_classes.find(&type) == registered_classes.end()) { // 执行实际的注册逻辑 // ... registered_classes.insert(&type); } }
这种方式不依赖编译选项或C++版本,通过运行时判断避免重复注册,缺点是会引入少量运行时开销。
方案4:调整CMake编译选项(不推荐)
CMake默认可能为GCC等编译器开启-fno-common选项,该选项会让每个编译单元中的全局静态变量生成独立实例。可在CMakeLists.txt中添加编译选项关闭该特性:
add_compile_options(-fcommon)
此方案依赖编译器特定行为,兼容性差,仅作为临时 workaround 使用。
内容的提问来源于stack exchange,提问作者ingotangjingle
相关产品推荐
相关产品推荐

