卡码笔记-最强八股文
首页
计算机基础
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
简历专栏
代码随想录 (opens new window)
首页
计算机基础
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
简历专栏
代码随想录 (opens new window)
  • 本栏必读

    • 关于本专栏
    • C++学习路线
    • C++面试题系优化
  • 基础与语法

  • 面向对象

    • C++构造函数有几种,分别什么作用?
    • 什么是构造函数和析构函数?构造函数和析构函数可以是虚函数吗?为什么?
    • C++的重载和重写,以及它们的区别和实现方式
    • C++怎么实现多态
    • C++中的虚函数和纯虚函数有什么区别?
    • 虚函数怎么实现的?
    • 多重继承的优缺点及菱形继承问题
    • 如何禁止一个类被继承
    • 深拷贝和浅拷贝的区别?
    • this指针的原理
    • C++如何实现一个单例模式?
      • 简要回答
      • 详细回答
      • 知识拓展
  • STL 与容器

  • 内存管理

  • C++11 与现代 C++

  • 智能指针

  • 并发与 I/O

# C++ 单例模式的实现

面试官问"怎么实现单例模式",大多数人能写出懒汉式,但追问"线程安全吗""双检锁有什么坑""现代 C++ 推荐哪种",就开始含糊了。

这道题考的不是能不能写出代码,而是你对四种实现方式的演进逻辑和各自的坑是否清楚。把"为什么一步步演进到 Magic Static"讲清楚,面试官就不会继续追了。

# 简要回答

单例模式确保类只有一个实例 + 提供全局访问点。核心手段:构造函数私有化 + 删除拷贝/赋值 + 静态方法返回唯一实例。现代 C++ 推荐用局部静态变量(Magic Static):static Singleton& getInstance() { static Singleton inst; return inst; }——C++11 标准保证局部 static 变量初始化是线程安全的,代码最简洁,自动析构,没有任何坑。

# 详细回答

四种实现的演进逻辑

饿汉式:程序启动时就创建实例(静态成员变量在 main 之前初始化)。天然线程安全,但如果单例很重且可能用不到,就浪费了资源。另外多个饿汉式单例之间的初始化顺序不确定(static initialization order fiasco)。

懒汉式:第一次调用 getInstance() 时才创建。解决了资源浪费问题,但多线程下两个线程同时判断 instance == nullptr 会创建两个实例——线程不安全。

双检锁(DCLP):在懒汉式基础上加锁。第一次检查避免每次都加锁(性能),加锁后第二次检查避免重复创建。但在 C++11 之前,由于指令重排和内存可见性问题,DCLP 实际上是有 bug 的。C++11 之后需要配合 std::atomic 或 std::call_once 才能正确实现。

Magic Static(C++11 推荐):函数内的局部 static 变量,C++11 标准规定其初始化必须是线程安全的(编译器负责加锁)。代码只需一行,没有手动锁、没有指针、没有内存泄漏(程序结束时自动析构)。这是现代 C++ 的标准答案。

为什么 Magic Static 是最优解

线程安全由编译器保证,不需要程序员操心。返回引用而非指针,不存在 delete 的问题。程序结束时自动调用析构函数,资源正确释放。代码极简,不容易写错。唯一的"缺点"是不能控制销毁顺序——但绝大多数场景不需要。

C++单例模式四种实现演进

# 知识拓展

Q:单例模式有什么缺点?什么时候不该用?

单例本质是全局状态,会导致:单元测试困难(全局状态难隔离)、隐藏依赖关系(调用方看不出依赖了单例)、违反单一职责(既管业务又管自己的生命周期)。如果可以用依赖注入替代,优先不用单例。真正适合的场景:日志器、配置管理器、线程池、连接池——这些天然是全局唯一且生命周期贯穿整个程序的。

Q:双检锁在 C++11 之前为什么有 bug?

instance = new Singleton() 这一行实际上分三步:分配内存、调用构造函数、赋值指针。编译器/CPU 可能重排为:分配内存→赋值指针→调用构造函数。这时另一个线程看到 instance != nullptr 就直接返回了,但对象还没构造完——未定义行为。C++11 的 std::atomic 和内存序解决了这个问题,但代码很复杂,不如直接用 Magic Static。

Q:饿汉式的初始化顺序问题是什么?

如果单例 A 的构造函数里用到了单例 B,而 B 还没初始化(不同编译单元的 static 变量初始化顺序未定义),就会崩溃。Magic Static 天然避免了这个问题——谁先被调用谁先初始化,顺序由调用链决定。

Q:单例怎么正确销毁?

Magic Static 方式程序结束时自动析构,不需要手动管理。如果用指针式(饿汉/懒汉/双检锁),需要额外的销毁机制(如 atexit 注册回调),否则内存泄漏。这也是推荐 Magic Static 的原因之一。

Last Updated: 5/23/2026, 4:51:07 PM

← this指针的原理 STL容器了解哪些 →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪