静态对象构造顺序不可控,但可确保所有插件注册在main()前完成;应使用字符串id或std::type_index注册,插件基类须有虚析构,windows dll需显式导出符号防注册失效。

静态对象构造顺序不可控,但能用它触发注册
静态变量的构造函数在 main() 之前执行,这是实现“自动注册”的底层依据。但不同编译单元里的静态对象初始化顺序是未定义的——这意味着你不能假设插件 A 一定比 B 先注册。真正能依赖的只有:所有注册动作都会在 main() 开始前完成。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 每个插件类(如
ImageLoaderPNG)定义一个匿名命名空间内的静态对象,其构造函数调用全局注册表的registerPlugin() - 注册表本身必须是函数局部静态变量(
Static PluginRegistry& instance()),避免跨 TU 初始化顺序问题 - 不要在注册器构造函数里调用其他插件的接口——它们可能还没构造
std::map 键值设计:用 std::type_info 还是字符串?
用 typeid(T).name() 当 key 看似自然,但实际不可靠:name() 返回的是编译器特定的 mangled 名字,不同平台/编译器结果不一致,且无法跨 DSO(动态库)比较。更稳的方式是用字符串 ID,由插件作者显式声明。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 注册接口统一为
registerPlugin(const std::String& id, std::unique_ptr<pluginbase> plugin)</pluginbase> - 插件实现里硬编码 ID,比如
"json_parser"或"png_decoder",不依赖类型推导 - 如果真要基于类型,改用
std::type_index{typeid(T)},它可比较、可哈希,且跨 TU 一致
插件基类必须有虚析构,否则 delete 会 UB
注册表通常持有 std::unique_ptr<pluginbase></pluginbase>,而实际对象是派生类实例。如果没有虚析构,unique_ptr 在销毁时只调用 PluginBase::~PluginBase(),派生类资源(如堆内存、文件句柄)不会释放——这是典型的未定义行为,且很难调试。
常见错误现象:
- 程序退出时崩溃(析构时跳转到非法地址)
- Valgrind 报告 “Mismatched free() / delete / delete []”
- 资源泄漏,但编译器不报错
实操建议:
立即学习“C++免费学习笔记(深入)”;
-
class PluginBase { public: virtual ~PluginBase() = default; };—— 必须写,不能省略 - 如果基类无其他虚函数,仅加这一行就能启用虚表,开销极小
- 不要用
= delete或空实现体替代= default,前者禁用析构,后者可能被优化掉虚表
Windows DLL 和 linux SO 的静态初始化差异
Linux 下全局构造函数默认生效;Windows 下 DLL 中的静态对象,若未被主程序任何符号引用,链接器可能直接丢弃整个 .obj——导致注册无声失效。这不是 c++ 标准问题,而是工具链行为差异。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 在 DLL/SO 的导出函数中,显式调用一个空的初始化函数(如
void init_plugin_png();),并在其中放一句(void)dummy;引用该插件的静态注册对象 - CMake 中对 Windows 添加
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON),或手动用__declspec(dllexport)标记至少一个符号 - 用
objdump -t your_plugin.dll | grep register(Linux)或dumpbin /symbols(Windows)确认注册符号是否真实存在
最麻烦的不是怎么写注册逻辑,而是验证它真的跑起来了——尤其在跨平台打包后,静态初始化失败往往静默发生,连日志都打不出来。