
引言在业务逻辑中常需确保某个对象在多线程中仅被创建一次那么如何实现呢答案就是单例模式本文章将对懒汉单例进行详细介绍局部静态变量函数内声明的静态变量对于局部静态变量其显著特征就是函数因多次调用导致静态变量定义语句被重复执行时只有第一次调用时进行初始化操作后续该函数的重复调用将会跳过该语句而该特征对于类内成员函数中的局部静态变量依旧成立具体来说// 成员函数的局部静态变量行为测试classTest{public:voidtest(){staticinta{};// 与全局函数的静态局部成员行为完全一致当该语句因函数的多次调用而被重复执行时只有第一次调用时进行初始化操作couta\n;}};voidtest1(){Test{}.test();// 1Test{}.test();// 2Test{}.test();// 3}运行结果表明a 只被初始化一次并在多次调用间保持状态这为“延迟初始化”提供了天然的语言级支持懒汉单例懒汉单例就需要借助局部静态变量的上述特点进行实现代码如下// 受上述启发得到的单例模式设计与测试classMyClass{public:staticMyClassInstance()// 获取单例的函数必须设计为静态原因是若非静态则需要实例才能调用而若具备实例也就不需要调用该函数了{staticMyClass mC{};// 核心创建局部静态变量作为单例returnmC;}~MyClass()default;// 设计单例模式需要将拷贝以及赋值重载进行deleteMyClass(constMyClassother)delete;MyClassoperator(constMyClassother)delete;// 注若显式delete拷贝构造及赋值重载则对应的移动语义不会再自动生成这里为了代码语义清晰性仍显示进行了移动语义deleteMyClass(MyClassother)delete;MyClassoperator(MyClassother)delete;voidtest(){cout_a\n;}private:MyClass()default;// 将构造函数设计为私有成员确保只有类的成员可以访问包括静态成员int_a{};};voidtest2(){MyClassmC1MyClass::Instance();// 如此设计的单例需使用引用类型接收mC1.test();// 0mC1.test();// 1MyClassmC2MyClass::Instance();mC2.test();// 2mC2.test();// 3}可以看到mC1和mC2实际上引用的是同一个对象。设计要点解析1.Instance()为什么必须是static如果Instance()是非静态成员函数调用它需要一个对象实例但获取实例正是我们调用它的目的这就形成了逻辑死循环。因此获取单例的接口必须是static。2. 为什么要把构造 / 拷贝 / 移动全部封死函数原因构造函数private防止外部随意构造对象拷贝构造 delete禁止逻辑上不合理的复制拷贝赋值 delete同上移动构造 / 赋值 delete显式表达“不可移动”避免隐式规则带来的误解提高代码可读性显式 delete是一种防御性编程手段比“依赖编译器隐式行为”更清晰、更安全3. 为什么构造函数可以是私有的虽然构造函数是private但Instance()是类的静态成员静态成员可以访问类的私有成员因此static MyClass mC{};是完全合法的。一个非常重要的线程安全说明C11C11 之前函数内静态变量初始化不是线程安全的多线程并发可能导致多次初始化。C11 起标准明确规定函数内静态变量初始化是线程安全的俗称Magic Statics。因此这种单例写法要求编译环境至少为 C11。小结本实现是懒汉式单例基于函数内静态变量Meyers SingletonC11 起天然线程安全无锁、无手动内存管理、生命周期由语言保证这也是 Scott Meyers 在Effective C中推荐的单例实现方式被广泛认为是现代 C 中最优雅的单例写法之一