Static initialization order fiasco 是指跨编译单元的 static 变量初始化顺序未定义,导致依赖调用时出现未定义行为;Construct on First Use 通过函数局部 static 变量延迟构造并返回引用,确保首次访问时才初始化,从而规避该问题。

什么是 static initialization order fiasco?
当多个 static 变量(尤其是跨编译单元)的初始化相互依赖时,C++ 标准不保证它们的初始化顺序。如果 A 的构造函数调用 B::get(),而 B 尚未初始化,就会触发未定义行为——常见表现是程序崩溃、空指针解引用或读取垃圾值。
为什么 Construct on First Use 能规避这个问题?
核心思路是:把 static 对象的构造延迟到**第一次使用时**,而非翻译单元加载时。这样能确保每次访问都拿到一个已构造完成的对象,彻底绕过初始化顺序问题。
- 只对函数局部
static变量有效(C++11 起保证线程安全初始化) - 全局
static或类静态数据成员仍受 fiasco 影响,不能直接用 - 每次调用函数开销极小(仅首次有构造成本,后续是纯指针返回)
如何正确实现 Construct on First Use?
必须用函数封装,且返回引用;不能返回值或指针(避免拷贝或悬空);函数体里声明局部 static 变量。
MyClass& getMyInstance() {
static MyClass instance; // ✅ 延迟构造,C++11 线程安全
return instance;
}- 错误写法:
static MyClass* p = new MyClass();—— 内存泄漏风险,且无法控制析构时机 - 错误写法:
return MyClass();—— 返回临时对象,调用者拿到的是已销毁对象的引用 - 若需自定义构造参数,可传入函数参数并缓存(如
static MyClass instance(arg);)
它不能解决哪些问题?
Construct on First Use 是“访问时保障”,不是“启动时保障”。如果你的逻辑依赖某个对象在 main() 之前就绪(比如用于全局 atexit() 回调、信号处理函数中访问),它就无能为力。
立即学习“C++免费学习笔记(深入)”;
- 静态对象的析构顺序仍是反向初始化顺序,和构造顺序无关
- 若两个函数互相调用对方的 “on-first-use” 函数(A 调 B,B 又调 A),会引发死锁或未定义行为
- 频繁调用该函数的性能敏感路径,需确认编译器是否内联了返回引用的操作(通常会)
真正棘手的地方往往不在“怎么写”,而在“要不要写”——有些模块看似需要全局单例,实则可通过依赖注入或显式生命周期管理更清晰地解耦。











