Skip to content

口述最简单的实现单例的 code

约 690 字大约 2 分钟

设计模式金山

2025-03-21

⭐ 题目日期:

金山 - 2024/12/31

📝 题解:

class Singleton {
public:
    // 获取单例对象的唯一接口(线程安全、延迟初始化)
    static Singleton& getInstance() {
        static Singleton instance; // C++11 保证静态局部变量初始化线程安全
        return instance;
    }

    // 删除拷贝构造函数和赋值操作符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    // 私有构造函数,禁止外部实例化
    Singleton() = default;
};

口述要点解析

  1. 线程安全

    • C++11 标准规定,静态局部变量的初始化是线程安全的,无需手动加锁。编译器会自动插入线程安全代码(如原子操作或互斥锁)。
    • 对比旧标准的“双重检查锁定”(DCLP),此实现更简洁且无潜在风险。
  2. 延迟初始化

    • 静态局部变量 instance 在首次调用 getInstance() 时初始化,避免不必要的资源占用。
  3. 防拷贝和防赋值

    • 显式删除拷贝构造函数和赋值操作符(= delete),防止通过拷贝或赋值创建新实例。
  4. 构造函数私有化

    • 将默认构造函数设为私有(private),禁止外部直接实例化对象。

常见追问与应对

  1. 为什么用静态局部变量而不是静态成员变量?

    • 静态成员变量需要在类外单独初始化(违反封装),而静态局部变量通过函数访问,保证唯一性和延迟初始化。
  2. 如何保证线程安全?

    • C++11 标准明确规定,静态局部变量的初始化由编译器保证线程安全,类似隐式加锁,但无性能损耗。
  3. 是否可以用指针返回单例?

    • 可以(如返回 Singleton*),但返回引用更安全,避免用户误调用 delete
  4. 是否需要考虑析构函数?

    • 单例通常生命周期与程序一致,无需手动释放。若需资源清理,可将析构函数设为私有,或在 getInstance() 中控制。

其他实现对比(补充说明)

  • 双重检查锁定(DCLP)

    // 不推荐!旧标准下的复杂实现,C++11 后已过时
    Singleton* Singleton::instance = nullptr;
    std::mutex Singleton::mutex;
    
    Singleton* Singleton::getInstance() {
        if (instance == nullptr) {          // 第一次检查
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) {      // 第二次检查
                instance = new Singleton();
            }
        }
        return instance;
    }
    • 缺点:需手动管理内存,旧编译器可能存在指令重排问题(需 volatile 或内存屏障),代码冗余。
  • 饿汉式单例

    // 程序启动时立即初始化,可能浪费资源
    class Singleton {
        static Singleton instance; // 在类外初始化
        // ...
    };
    • 缺点:无法延迟初始化,可能影响启动速度。