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
}

可以看到,mC1mC2 实际上引用的是同一个对象

设计要点解析

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++ 中最优雅的单例写法之一


相关推荐
脱胎换骨-军哥16 小时前
C++分布式系统设计:从通信引擎到分布式共识
开发语言·c++·分布式
脱胎换骨-军哥17 小时前
C++零成本抽象理论深度拆解:现代C++如何在不牺牲性能的前提下提供高级语法封装
java·开发语言·c++
lbb 小魔仙17 小时前
VS Code Python 高级调试技巧:从入门到精通
开发语言·python
啊哦11117 小时前
安装VMware Workstation
linux·运维·服务器
_wyt00117 小时前
01背包问题详解
c++·背包dp
方便面不加香菜17 小时前
C++ 模板进阶
开发语言·c++
小白说大模型17 小时前
从向量嵌入到复杂 Agent:LLM、LangChain、LangGraph 完整科普
java·开发语言·人工智能·gpt·深度学习·langchain
Wild_Pointer.18 小时前
高效工具实战指南:Procexp64进程资源管理器
c++·windows
持敬chijing18 小时前
Linux基础命令与目录命令
linux·运维·服务器