C++懒汉单例设计详解

引言

在业务逻辑中,常需确保某个对象在多线程中仅被创建一次,那么,如何实现呢?答案就是单例模式,本文章将对懒汉单例进行详细介绍

局部静态变量(函数内声明的静态变量)

对于局部静态变量,其显著特征就是函数因多次调用导致静态变量定义语句被重复执行时,只有第一次调用时进行初始化操作,后续该函数的重复调用将会跳过该语句,而该特征对于类内成员函数中的局部静态变量依旧成立,具体来说:

cpp 复制代码
// 成员函数的局部静态变量行为测试
class Test
{
public:
	void test()
	{
		static int a{};// 与全局函数的静态局部成员行为完全一致,当该语句因函数的多次调用而被重复执行时,只有第一次调用时进行初始化操作
		cout << ++a << "\n";
	}
};

void test1()
{
	Test{}.test();// 1
	Test{}.test();// 2
	Test{}.test();// 3
}

运行结果表明:a 只被初始化一次,并在多次调用间保持状态 ,这为"延迟初始化"提供了天然的语言级支持

懒汉单例

懒汉单例就需要借助局部静态变量的上述特点进行实现,代码如下:

cpp 复制代码
// 受上述启发得到的单例模式设计与测试
class MyClass
{
public:
	static MyClass& Instance()// 获取单例的函数必须设计为静态,原因是,若非静态,则需要实例才能调用,而若具备实例也就不需要调用该函数了
	{
		static MyClass mC{};// 核心,创建局部静态变量作为单例
		return mC;
	}
	~MyClass() = default;

	// 设计单例模式,需要将拷贝以及赋值重载进行delete
	MyClass(const MyClass& other) = delete;
	MyClass& operator=(const MyClass& other) = delete;
	// 注:若显式delete拷贝构造及赋值重载,则对应的移动语义不会再自动生成,这里为了代码语义清晰性仍显示进行了移动语义delete
	MyClass(MyClass&& other) = delete;
	MyClass& operator=(MyClass&& other) = delete;
	void test()
	{
		cout << _a++ << "\n";
	}
private:
	MyClass() = default;// 将构造函数设计为私有成员,确保只有类的成员可以访问(包括静态成员)
	int _a{};
};

void test2()
{
	MyClass& mC1 = MyClass::Instance();// 如此设计的单例,需使用引用类型接收
	mC1.test();// 0
	mC1.test();// 1
	MyClass& mC2 = MyClass::Instance();
	mC2.test();// 2
	mC2.test();// 3
}

可以看到,mC1 和 mC2 实际上引用的是同一个对象。

设计要点解析

1. Instance() 为什么必须是 static

如果 Instance() 是非静态成员函数:

  • 调用它需要一个对象实例
  • 但获取实例正是我们调用它的目的

这就形成了逻辑死循环。

因此,获取单例的接口必须是 static。


2. 为什么要把构造 / 拷贝 / 移动全部封死

函数 原因
构造函数 private 防止外部随意构造对象
拷贝构造 = delete 禁止逻辑上不合理的复制
拷贝赋值 = delete 同上
移动构造 / 赋值 = delete 显式表达"不可移动",避免隐式规则带来的误解,提高代码可读性

显式 = delete 是一种防御性编程手段,比"依赖编译器隐式行为"更清晰、更安全


3. 为什么构造函数可以是私有的?

虽然构造函数是 private,但:

  • Instance() 是类的静态成员
  • 静态成员可以访问类的私有成员

因此,static MyClass mC{}; 是完全合法的。


一个非常重要的线程安全说明(C++11+)

  • C++11 之前 :函数内静态变量初始化不是线程安全的,多线程并发可能导致多次初始化。
  • C++11 起 :标准明确规定,函数内静态变量初始化是线程安全的 (俗称 Magic Statics)。

因此,这种单例写法要求编译环境至少为 C++11。


小结

  • 本实现是懒汉式单例
  • 基于函数内静态变量(Meyers Singleton)
  • C++11 起天然线程安全
  • 无锁、无手动内存管理、生命周期由语言保证

这也是 Scott Meyers 在 Effective C++ 中推荐的单例实现方式,被广泛认为是现代 C++ 中最优雅的单例写法之一


相关推荐
第九序言T11 分钟前
Python 数据处理进阶(十八)批量处理同结构文件:一个文件夹一次跑完
开发语言·python·自动化·pandas·数据处理
欧米欧23 分钟前
Linux系统篇(七),环境变量的底层溯源,核心获取机制与安全应用
linux·运维·服务器
Ruiery33 分钟前
Linux 6.6内核内存管理深度解析(十八):KASLR 内核地址空间随机化
linux·运维·服务器·安全
Sakura_j_h_41 分钟前
EasyEDA PCB 快速设计Skills
c++·python·github
前端精髓1 小时前
调用大模型API
运维·服务器·microsoft
龙亘川1 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
程序喵大人2 小时前
【C++入门】内存模型与资源管理 - 09 shared_ptr 和 weak_ptr 解决共享,也带来代价
开发语言·c++·资源管理·智能指针·内存模型
Raas1002 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
专注仿真3 小时前
线性回归和逻辑回归算法详解
开发语言·人工智能·python·大模型训练·控制算法
逆风的蚂蚁3 小时前
linux服务器性能调整怎么落地?我把 CPU 调度、I/O 优先级和内核参数这几个旋钮都拧了一遍
linux·运维·服务器