从第一性原理理解单例模式

我们想要一个类,在整个程序运行期间,它只能被创建出一个对象实例。

为什么需要?比如:配置管理器、日志输出器、线程池------如果出现多个实例,可能导致数据冲突、资源浪费,或者违反业务逻辑(比如一个进程只能有一个打印机控制对象)。

那么问题来了:C++ 中,怎样才能强行限制一个类只能有一个实例?


第一步:禁止外部随意创建对象

正常情况下,你可以写:

cpp 复制代码
class MyClass {
public:
    MyClass() {}   // 公有构造函数
};
MyClass a;        // 可以创建
MyClass b;        // 又可以创建

要阻止这种情况,必须让外界无法调用构造函数 。把构造函数声明为 private 即可:

cpp 复制代码
class Singleton {
private:
    Singleton() {}   // 私有构造,外部不能 new
};

现在外部写 Singleton s; 会编译错误。

但光这样还不够,这个类连一个对象都造不出来了,我们要的只是唯一一个 ,不是零个。所以需要提供一个公开的接口来获取那唯一的一个实例。


第二步:提供一个全局访问点

我们需要一个静态成员函数 ,用它来返回唯一的实例。同时需要保存这个唯一实例的指针(或对象)。

保存的地方也只能是类的静态成员,因为只有静态成员属于类本身,而非某个对象。

cpp 复制代码
class Singleton {
private:
    Singleton() {}
    static Singleton* instance_;   // 静态指针,存放唯一实例的地址
public:
    static Singleton* getInstance() {
        if (instance_ == nullptr) {
            instance_ = new Singleton();
        }
        return instance_;
    }
};

// 静态成员在类外初始化
Singleton* Singleton::instance_ = nullptr;

用法:

cpp 复制代码
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
// p1 和 p2 指向同一个对象

此时我们实现了"懒汉式"单例:第一次调用 getInstance() 时才创建实例。


第三步:杜绝拷贝和赋值

即使构造函数私有,外部仍然可能通过拷贝构造或赋值来制造"看起来像第二个实例"的对象:

cpp 复制代码
Singleton* p1 = Singleton::getInstance();
Singleton s2 = *p1;   // 拷贝构造(如果编译器默认生成公有拷贝构造)

为了防止这种情况,需要禁止拷贝构造和赋值操作 。C++98 经典做法是把它们声明为私有且不定义;C++11 及以后可以用 = delete

cpp 复制代码
class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    // ...
};

现在任何企图拷贝或赋值的代码都会编译报错,保证真的只有一个实例。


第四步:线程安全(多线程环境)

上面的懒汉式 if (instance_ == nullptr) 在多线程下不安全:两个线程同时进入判断,都可能发现指针为空,然后各自 new,破坏单例。

加锁可以解决,但 C++11 之后有一个更优雅、零开销的线程安全单例写法 ------ Meyers Singleton ,它利用函数内静态局部变量的特性。

C++11 标准保证:函数内的静态局部变量在第一次控制流经过其声明时初始化,且初始化是线程安全的

cpp 复制代码
class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    static Singleton& getInstance() {
        static Singleton instance;   // 第一次调用时才构造,并且线程安全
        return instance;
    }
};

用法:

cpp 复制代码
Singleton& s = Singleton::getInstance();

这里返回的是引用而非指针,更加简洁,也不需要手动 delete。在程序结束时,静态局部变量会自动销毁,生命周期管理由编译器负责。


完整代码示例

cpp 复制代码
#include <iostream>

class Singleton {
private:
    Singleton() { 
        std::cout << "Singleton created" << std::endl; 
    }
    ~Singleton() { 
        std::cout << "Singleton destroyed" << std::endl; 
    }

public:
    // 禁用拷贝和移动
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;

    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }

    void doSomething() const {
        std::cout << "Doing something..." << std::endl;
    }
};

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    // s1 和 s2 引用的是同一个对象
    s1.doSomething();
    return 0;
}

输出:

cpp 复制代码
Singleton created
Doing something...
Singleton destroyed
需求 对应的技术措施
只有一个实例 私有构造函数,删除拷贝/赋值操作
提供全局访问点 静态成员函数 getInstance
控制实例创建时机 懒汉式(首次使用时创建)或饿汉式(预先创建)
多线程环境下保证唯一性 C++11 静态局部变量(Meyers Singleton)
自动销毁实例 静态局部变量的 RAII 特性
相关推荐
程序员榴莲19 小时前
Python 单例模式
开发语言·python·单例模式
小江的记录本2 天前
【Java并发编程】锁机制:volatile:JMM内存模型、可见性/禁止指令重排、内存屏障、单例模式中的应用(附《思维导图》+《面试高频考点清单》)
java·后端·python·mysql·单例模式·面试·职场和发展
计算机安禾3 天前
【c++面向对象编程】第40篇:单例模式(Singleton)的多种C++实现
开发语言·c++·单例模式
basketball6165 天前
C++ 单例模式完全指南:从饿汉式到现代 C++ 的最佳实践
java·c++·单例模式
W.W.H.5 天前
Qt 应用防多开:极简单例方案
开发语言·qt·单例模式·共享内存
++==5 天前
设计模式:单例模式和观察者模式实现方式以及优化
观察者模式·单例模式·设计模式
摇滚侠8 天前
Java 饿汉式 单例模式
java·开发语言·单例模式
游乐码9 天前
Unity坦克案例疑难记录(一)
unity·单例模式
想学会c++12 天前
单例模式笔记总结
c++·笔记·单例模式