代码仓库shanchuann/CPP-Learninng
单例模式是保证一个类仅有一个实例,并提供全局访问点的设计模式,广泛应用于日志系统、配置管理、数据库连接池等需要全局唯一实例的场景。C++ 实现单例模式的核心是私有化构造函数、禁用拷贝构造与赋值运算符,根据实例创建时机分为懒汉模式和饿汉模式,二者在线程安全性、性能和资源占用上存在明显差异,本文结合完整可运行代码,详解四种经典实现方式。
饿汉模式 饿汉模式的核心是预初始化,即程序启动时(main函数执行前)就创建单例实例 ,无需延迟初始化,天然具备线程安全性,无需额外的同步机制。
静态成员变量饿汉模式(线程安全) 将单例实例定义为类的静态成员变量,在类外部完成初始化,程序加载阶段就创建实例,调用getInstance时直接返回实例引用,无空值判断和锁开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Singleton {private : Singleton (int val = 0 ):value (val) { cout << "Singleton 构造函数被调用" << endl; } Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; ~Singleton () { cout << "Singleton 析构函数被调用" << endl; } static Singleton s; int value; public : static Singleton& getInstance () { return s; } void show () { cout << "Singleton: " << value << endl; } }; Singleton Singleton::s (10 ) ;
1 2 3 4 5 6 7 int main () { Singleton& instance1 = Singleton::getInstance (); instance1. show (); Singleton& instance2 = Singleton::getInstance (); instance2. show (); return 0 ; }
该实现的缺点是程序启动时就占用内存,即使实例从未被使用,适合实例使用频率高、对启动性能无要求的场景。
静态局部变量饿汉模式(C++11前线程不安全) 在 C++11 之前,静态局部变量的初始化不是线程安全的,这也是早期需要双检锁等复杂实现的原因。但 C++11 标准彻底解决了这个问题。当多个线程首次同时执行到 static Singleton s(10); 这一行时,只有一个线程会执行初始化逻辑,其他线程会被阻塞,直到该静态变量完全初始化完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Singleton {private : Singleton (int val = 0 ) :value (val) { cout << "Singleton 构造函数被调用" << endl; } Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; ~Singleton () { cout << "Singleton 析构函数被调用" << endl; } int value; public : static Singleton& getInstance () { static Singleton s (10 ) ; return s; } void show () { cout << "Singleton: " << value << endl; } };
“If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.”(如果控制流在变量初始化期间并发进入声明,并发执行将等待初始化完成。)
1 2 3 4 5 6 7 int main () { Singleton& instance1 = Singleton::getInstance (); instance1. show (); Singleton& instance2 = Singleton::getInstance (); instance2. show (); return 0 ; }
该实现结合了懒汉模式的延迟初始化和饿汉模式的线程安全,代码极简,无需手动管理互斥锁和原子操作,是 C++11 及以上版本的首选单例实现方式。
懒汉模式 懒汉模式的核心是延迟初始化,即程序运行过程中首次使用实例时才创建对象 ,避免程序启动时的资源占用,是开发中常用的单例实现方式。
基础懒汉模式(线程不安全) 基础懒汉模式是最简单的实现,无任何线程同步机制,仅适用于单线程环境。多线程并发调用时,多个线程可能同时通过空值判断,导致创建多个实例,违背单例原则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Singleton {private : Singleton (int val = 0 ) :value (val) { cout << "Singleton 构造函数被调用" << endl; } Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; ~Singleton () { cout << "Singleton 析构函数被调用" << endl; } static Singleton* ps; int value; public : static Singleton* getInstance () { if (ps == nullptr ) { ps = new Singleton (10 ); } return ps; } void show () { cout << "Singleton: " << value << endl; } }; Singleton* Singleton::ps = nullptr ; void funa () { Singleton* instance = Singleton::getInstance (); instance->show (); } void funb () { Singleton* instance = Singleton::getInstance (); instance->show (); }
1 2 3 4 5 int main () { funa (); funb (); return 0 ; }
该实现通过私有构造函数阻断外部实例化,用delete关键字禁用拷贝与赋值,静态指针ps初始化为空,首次调用getInstance时完成实例创建,后续调用直接返回已创建的指针。
双检锁懒汉模式(线程安全) 为解决基础懒汉模式的线程安全问题,采用双检锁(Double-Checked Locking)结合原子操作实现,既保证线程安全,又避免了每次获取实例都加锁的性能开销。C++ 中指令重排会导致双检锁失效,因此需要通过std::atomic和指定内存序保证实例创建的完整性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Singleton {private : Singleton (int val = 0 ) :value (val) { cout << "Singleton 构造函数被调用" << endl; } Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; ~Singleton () { cout << "Singleton 析构函数被调用" << endl; } static std::atomic<Singleton*> ps; static std::mutex mtx; int value; public : static Singleton* getInstance () { Singleton* tmp = ps.load (std::memory_order_acquire); if (tmp == nullptr ) { std::lock_guard<std::mutex> lk (mtx) ; tmp = ps.load (std::memory_order_relaxed); if (tmp == nullptr ) { tmp = new Singleton (10 ); ps.store (tmp, std::memory_order_release); } } return tmp; } void show () { cout << "Singleton: " << value << endl; } }; std::atomic<Singleton*> Singleton::ps{ nullptr }; std::mutex Singleton::mtx; void funa () { Singleton* instance = Singleton::getInstance (); instance->show (); } void funb () { Singleton* instance = Singleton::getInstance (); instance->show (); }
实现中std::mutex保证临界区的互斥访问,两次空值判断大幅减少锁竞争,std::atomic配合memory_order_acquire和memory_order_release内存序,防止编译器和CPU的指令重排,确保实例创建完成后才对其他线程可见。
单例模式的析构函数私有化设计,保证实例无法被外部手动销毁,静态变量的生命周期与程序完全一致,程序退出时会自动调用析构函数释放资源。禁用拷贝构造和赋值运算符是单例模式的核心约束,从语法层面杜绝通过拷贝创建新实例的可能,保证实例的全局唯一性。