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


相关推荐
Darkwanderor9 分钟前
线程概念和Linux线程
linux·c语言·c++
clear sky .15 分钟前
[freertos源码阅读]task.c任务篇
c语言·开发语言
lingran__17 分钟前
C++ 高阶数据结构:红黑树万字详解|完整原理推导 + 插入实现 + 完整性校验【STL 底层】
数据结构·c++·面试·红黑树·二叉搜索树·平衡二叉树·stl底层
XR12345678835 分钟前
工业园区整体组网,方案对比怎么选?
开发语言·php
东东最爱敲键盘41 分钟前
day9.C++多态之虚函数
开发语言·c++
酷可达拉斯44 分钟前
自动化运维-从零开始编写ansible剧本(二)
linux·运维·服务器·centos·自动化
码云数智-园园1 小时前
类似小鹅通的知识付费小程序平台有哪些
开发语言
Darkwanderor1 小时前
Linux线程控制
linux·c语言·c++
snow@li1 小时前
Java:跨平台原理与JDK、JRE、JVM全景深度解析
java·开发语言·jvm
Livia要学习1 小时前
Python闭包
开发语言·jvm·python