最稳妥的单例实现是Meyer单例:函数内静态局部变量,C++11线程安全、自动构造与析构;析构函数仅做轻量清理,禁用其他单例;依赖对象也须同方式实现;unique_ptr若作静态局部变量才安全;GC类需类外定义静态实例;shutdown()应由业务层显式调用。

程序退出时单例不销毁,不是因为忘了写析构函数,而是堆上分配的静态指针(m_instance)本身不会触发所指对象的析构——系统只管静态变量生命周期,不管它指向哪。最稳妥的做法是根本别用堆分配,直接让单例对象落在静态存储期里。
用函数内静态局部变量实现 Meyer 单例
这是 C++11 起线程安全、自动构造+自动析构的默认解法,彻底绕过跨编译单元析构顺序问题。
-
static Singleton& getInstance()中的static Singleton instance是真正的静态对象,不是指针,它的析构由运行时保证在main()返回后执行 - 构造发生在首次调用
getInstance()时,天然延迟初始化,且 C++11 标准要求线程安全 - 析构函数里只能做轻量清理:关文件描述符、
munmap、pthread_cancel等,不能调用其他单例(比如Logger::instance().log()),否则可能访问已销毁对象 - 如果单例依赖外部资源(如全局
std::filesystem::path或另一个单例),必须确保这些依赖也用同样 Meyer 方式实现,否则析构链断裂
用 std::unique_ptr 封装堆单例时的陷阱
有人图省事写 static std::unique_ptr<singleton> inst = std::make_unique<singleton>()</singleton></singleton>,看似自动管理,但风险藏在存储位置。
- ✅ 正确:把
std::unique_ptr放在getInstance()函数内部作为静态变量——它本身是静态的,析构时机可控 - ❌ 错误:把它声明为全局变量或命名空间作用域静态变量——会掉进静态初始化顺序问题(SIOF),可能在构造前就被其他模块访问
- ⚠️ 注意:
std::unique_ptr的析构函数仍要判空,尤其当构造函数抛异常导致对象未完全构建时,reset()可能被多次调用
垃圾回收类(GC 类)必须显式定义才生效
很多人写了内部 class GC,却漏掉最关键一步:类外定义静态成员实例。没这行,GC 构造函数根本不会跑,析构更无从谈起。
- 类内只声明:
static GC garbo; - 必须在 .cpp 文件中补上:
Singleton::GC Singleton::garbo; -
GC::~GC()里必须先检查m_instance != nullptr,再delete m_instance,否则重复析构或空指针解引用直接崩 - 如果单例构造函数加了条件判断(例如只在配置开启时 new),
m_instance可能一直为nullptr,不判空就 crash
真正难处理的不是“怎么销毁”,而是“销毁时还能依赖什么”。哪怕用了 Meyer 单例,只要析构函数里调了还没析构完的其他全局对象,或者主线程提前 exit 导致其他工作线程还在跑,就可能踩到竞态或访问释放内存。所以关键资源最好配一个显式的 shutdown() 方法,由业务层在合适时机调用,而不是全押给析构函数。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











