单例模式
类图如下

| 饿汉 | 懒汉 | |
|---|---|---|
| 特点 | 线程安全,实现简单 | 需要时才创建,节省资源 |
| 缺点 | 可能浪费资源 | 需要额外同步机制 |
饿汉式
EagerSingleton.h
class EagerSingleton {
public:
static EagerSingleton* getInstance();
///如果你不显式删除拷贝构造和赋值运算符,C++ 编译器会自动生成它们
EagerSingleton(const EagerSingleton&)=delete;
EagerSingleton& operator=(const EagerSingleton&)=delete;
// 显式删除移动操作,当显式声明或delete了复制操作后,
// 编译器不会自动创建移动操作,因此不用显式删除移动操作也可以
EagerSingleton(EagerSingleton&&) = delete;
EagerSingleton& operator=(EagerSingleton&&) = delete;
private:
EagerSingleton()=default;
~EagerSingleton()=default;
static EagerSingleton *m_instance;
};
EagerSingleton.cpp
// 类外定义并初始化,这里实际分配内存并调用构造函数
EagerSingleton* EagerSingleton::m_instance = new EagerSingleton();
EagerSingleton* EagerSingleton::getInstance()
{
return m_instance;
}
适用场景:
- 场景1:启动时必须初始化的核心组件,如日志组件
- 场景2:配置相关的单例
- 场景3: 初始化环境变量
关于静态变量的补充说明
在C++中,静态变量(包括全局变量和类的静态成员变量)的初始化分为两种:
静态初始化:在程序加载时完成,通常用于初始化内置类型(如int、指针等)的常量表达式。
动态初始化:对于需要执行构造函数等复杂初始化的类型,则需要进行动态初始化。
饿汉式的单例通常属于动态初始化。C++标准规定,动态初始化的非局部变量(即全局变量和静态成员变量)的初始化发生在main函数执行之前,
但是同一个编译单元内的变量初始化顺序是按照定义顺序进行的,而不同编译单元之间的初始化顺序是未定义的。
所以,饿汉式单例的初始化发生在main函数之前,但具体时间点由编译器决定,并且跨编译单元的初始化顺序是不确定的。
解决懒汉式中的线程问题
实现一:简单加锁
class Singleton {
private:
static Singleton* m_instance;
static std::mutex m_mutex;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> lock(m_mutex); // 方法级锁
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
};
// 静态成员初始化
Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutex;
问题:锁只有在实例未建立的时候才是必须的,一旦实例创建成功,后续的锁操作(锁的获取和释放)纯粹是性能开销。在高并发场景下,这会成为瓶颈。
实现二:双重检查锁定机制
双重检查锁定机制的核心思想是:加锁之前和之后各检查一次实例指针
- 看似正确实际错误的实现
// 警告:此代码有严重问题,不能使用!
class Singleton {
private:
static Singleton* instance;
static std::mutex mutex_;
Singleton() {}
public:
static Singleton* getInstance() {
// 第一次检查 (无锁)
if (instance == nullptr) {
// 加锁
std::lock_guard<std::mutex> lock(mutex_);
// 第二次检查 (有锁)
if (instance == nullptr) {
instance = new Singleton(); // 问题所在!
}
}
return instance;
}
};
问题根源:指令重排序
问题出在 instance = new Singleton(); 这行代码。它并非一个原子操作,通常包含三个步骤:
Step 1: 分配内存给 Singleton 对象。
Step 2: 在分配的内存上调用构造函数,初始化 Singleton 对象。
Step 3: 将内存地址赋值给 instance 指针。
编译器或 CPU 为了优化性能,可能会进行指令重排序,不能保证2 和 3的执行顺序
使用原子操作和内存序来确保正确的顺序
LazySingleton.h
#include "mutex"
///懒汉式单例
class LazySingleton {
public:
static LazySingleton *getInstance();
LazySingleton(const LazySingleton &) = delete;
LazySingleton &operator=(const LazySingleton &) = delete;
private:
LazySingleton() = default;
~LazySingleton() = default;
static std::atomic<LazySingleton*> m_instance;
static std::mutex m_mutex; //锁也必须是静态的,这样才能在所有对象实例间共享同一个锁
};
LazySingleton.cpp
//延迟构造--用的时候再进行构造
std::atomic<LazySingleton*> LazySingleton::m_instance{nullptr};
std::mutex LazySingleton::m_mutex;
LazySingleton* LazySingleton::getInstance()
{
auto tmp = m_instance.load(std::memory_order_acquire);
if (tmp == nullptr)
{
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr)
{
tmp = new LazySingleton();
/// 确保构造完成之后再赋值调用
m_instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
实现三:call_once
- std::once_flag 和 std::call_once 提供了一种线程安全的一次性初始化机制,适用于单例模式等场景。它们比手动实现双重检查锁定更安全、更简洁。
CallOnceLazySingleton.h
#pragma once
#include <mutex>
class CallOnceLazySingleton {
public:
static CallOnceLazySingleton *getInstance();
CallOnceLazySingleton(const CallOnceLazySingleton &) = delete;
CallOnceLazySingleton &operator=(const CallOnceLazySingleton &) = delete;
private:
static void createInstance();
CallOnceLazySingleton() = default;
~CallOnceLazySingleton() = default;
static CallOnceLazySingleton* m_instance;
static std::once_flag onceFlag;
};
CallOnceLazySingleton.cpp
CallOnceLazySingleton* CallOnceLazySingleton::m_instance{nullptr};
std::once_flag CallOnceLazySingleton::onceFlag;
CallOnceLazySingleton* CallOnceLazySingleton::getInstance()
{
std::call_once(onceFlag,createInstance);
return m_instance;
}
void CallOnceLazySingleton::createInstance()
{
m_instance = new CallOnceLazySingleton();
};
实现四:静态局部变量
C++11之后规定了局部静态变量的初始化是线程安全的,编译器会自动的生成等效于双重检查锁定的线程安全代码
- Scott Meyer在《Effective C++》中提出了一种简洁的singleton的写法
class Singleton{
static Singleton& getInstance(){
static Singleton instance;
return instance;
}
private:
Singleton()=default;
}
实现五:优雅简洁的工程实现
- 使用了模板,可以轻松为任何类型创建单例
Singleton.h
#pragma once
template <typename T>
class Singleton {
public:
static T& Instance();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
template <typename T>
T& Singleton<T>::Instance() {
static T instance;
return instance;
}