文章目录

  • 一、题目
  • 二、考察点
  • 三、答案
    • 3.1 C++11写法
    • 3.2 C++98写法(线程安全只存在于懒汉模式)
      • 3.2.1 小菜写法
      • 3.2.2 小菜进阶写法
      • 3.2.3 中登写法
      • 3.2.3 老鸟写法
  • 四、扩展知识
    • 4.1 饿汉模式和懒汉模式的区别
      • 4.1.1 饿汉模式(Eager Initialization)
      • 4.1.2 懒汉模式(Lazy Initialization)
    • 4.2 类中的成员变量啥时候初始化?
      • 4.2.1 普通成员变量(非静态、非 const)
      • 4.2.2 静态成员变量(`static`)
      • 4.2.3 常量成员变量(`const`)
      • 4.2.4 引用成员变量(`&`)
      • 4.2.5 总结:核心原则
  • 五、整体答案

一、题目

设计一个类,只能生成该类的一个实例。

二、考察点

singleton模式!

单例模式是指实现了特殊模式的类,该类仅能被实例化一次,产生唯一的一个对象。其常见的实现方式有饿汉式、懒汉式、双检锁、静态内部类、枚举等,评价指标包括是否为单例、线程安全、是否支持延迟加载、能否防止反序列化产生新对象以及防止反射攻击等。

三、答案

3.1 C++11写法

#include <iostream>
using namespace std;
class Singleton
{
public:static Singleton* getInstance(){static Singleton* instance;return instance;}
private:Singleton() = default;Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton& other) = delete;
};int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();// 验证是否为同一个实例cout << (s1 == s2 ? "是单例" : "不是单例") << endl; // 输出:是单例
}

这样的写法是C++11这样写的话,是满足线程安全的,方便快捷!

3.2 C++98写法(线程安全只存在于懒汉模式)

当然我们讨论线程安全,都是基于懒汉模式的!饿汉模式天然具有线程安全这一特性!

3.2.1 小菜写法

#include <iostream>class Singleton {
public:// 首次调用时创建实例static Singleton* getInstance() {if (instance == NULL) {instance = new Singleton();}return instance;}// 测试方法void print() {std::cout << "Singleton instance address: " << this << std::endl;}private:// 私有构造函数Singleton() {}// 禁用拷贝构造和赋值Singleton(const Singleton&);Singleton& operator=(const Singleton&);// 静态指针成员static Singleton* instance;
};

懒汉模式的线程安全问题源于其延迟初始化的特性 —— 实例在首次调用getInstance()时才创建,而非程序启动时。在多线程环境下,若多个线程同时进入 “未创建实例” 的代码分支,可能导致多个实例被创建,破坏单例的唯一性。

具体过程拆解(以 C++98 懒汉模式为例)

假设懒汉模式的getInstance()实现如下(简化版):

Singleton* Singleton::getInstance() {if (instance == NULL) {  // 检查实例是否已创建instance = new Singleton();  // 创建实例}return instance;
}

多线程并发时,问题可能这样发生:

  1. 线程 A进入if (instance == NULL)判断,发现未创建实例,准备执行new操作。
  2. 线程 B线程 A 执行new之前,也进入if判断,此时instance仍为NULL,因此也会执行new操作。
  3. 最终,线程 A 和线程 B 各自创建了一个实例,instance指针被两次赋值,导致单例被破坏(两个不同的实例同时存在)。

核心原因总结:

  • 判断与创建的非原子性if (instance == NULL)new Singleton()是两个独立的操作,而非一个不可分割的原子操作。
  • 并发抢占:多线程在 “判断为空” 到 “实际创建” 的间隙中可能同时进入临界区,导致重复创建。

这就是为什么懒汉模式在多线程环境下必须通过加锁(如pthread_mutex_lock)等同步机制保证线程安全,而饿汉模式因实例在程序启动时(单线程阶段)就已创建,天然不存在此问题。

3.2.2 小菜进阶写法

#include <iostream>
#include <mutex>
using namespace std;
mutex mtx;
class Singleton
{
public:static Singleton* getInstance(){if (nullptr == m_instance){mtx.lock();m_instance = new Singleton;mtx.unlock();}return m_instance;}
private:static Singleton* m_instance ;Singleton() = default;Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton& other) = delete;
};int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();// 验证是否为同一个实例cout << (s1 == s2 ? "是单例" : "不是单例") << endl; // 输出:是单例
}

问题:直接使用 mtx.lock()mtx.unlock() 也存在风险:

  • 如果 new Singleton 过程中抛出异常,mtx.unlock() 将不会执行,导致锁永远无法释放(死锁)。

  • 正确做法是使用:

    lock_guard
    

    (RAII 机制),确保锁在任何情况下都能自动释放:

    if (nullptr == m_instance) {lock_guard<mutex> lock(mtx);  // 自动加锁,作用域结束时自动解锁// ... 创建实例 ...
    }
    

3.2.3 中登写法

#include <iostream>
#include <mutex>
using namespace std;
mutex mtx;
class Singleton
{
public:static Singleton* getInstance(){if (nullptr == m_instance){lock_guard<mutex> lock(mtx);m_instance = new Singleton;}return m_instance;}
private:static Singleton* m_instance ;Singleton() = default;Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton& other) = delete;
};int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();// 验证是否为同一个实例cout << (s1 == s2 ? "是单例" : "不是单例") << endl; // 输出:是单例
}

问题: 缺少二次检查(双检锁必要步骤)

即使修正了前两个问题,单重检查加锁仍有漏洞:

  • 线程 A 检查 m_instance 为空后加锁,在执行 new 前被挂起。
  • 线程 B 同样检查 m_instance 为空,等待线程 A 释放锁。
  • 线程 A 释放锁后,线程 B 获得锁,再次创建实例(导致两个实例)。

解决:加锁后必须再次检查 m_instance 是否为空(双检锁)

3.2.3 老鸟写法

#include <iostream>
#include <mutex>
using namespace std;
mutex mtx;
class Singleton
{
public:static Singleton* getInstance(){if (nullptr == m_instance){lock_guard<mutex> lock(mtx);if (nullptr == m_instance){m_instance = new Singleton;}}return m_instance;}
private:static Singleton* m_instance ;Singleton() = default;Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton& other) = delete;
};int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();// 验证是否为同一个实例cout << (s1 == s2 ? "是单例" : "不是单例") << endl; // 输出:是单例
}

双检锁机制

  • 第一次检查(if (nullptr == m_instance)):未加锁,快速判断实例是否已创建,避免每次调用都加锁,提高性能。
  • 第二次检查:加锁后再次判断,防止多个线程同时通过第一次检查后重复创建实例

四、扩展知识

4.1 饿汉模式和懒汉模式的区别

饿汉模式和懒汉模式是单例模式中两种最常见的实现方式,核心区别在于实例创建的时机,以及由此衍生的线程安全、资源效率等差异:

4.1.1 饿汉模式(Eager Initialization)

  • 核心特点程序启动时(类加载阶段)就创建实例,无论后续是否使用。

  • 实现示例:

    class Singleton {
    private:// 静态成员变量,类加载时初始化static Singleton instance;// 私有构造函数Singleton() {}// 禁用拷贝和赋值Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 直接返回已创建的实例static Singleton& getInstance() {return instance;}
    };// 类外初始化静态成员(程序启动时执行)
    Singleton Singleton::instance;
    
  • 优缺点

    • 优点:
      • 实现简单,无需考虑线程安全问题(初始化在程序启动时完成,早于多线程启动)。
      • 不存在并发访问风险,性能稳定。
    • 缺点:
      • 提前占用内存,即使程序全程未使用该实例,也会消耗资源(尤其对资源密集型单例不友好)。
      • 若单例依赖其他初始化逻辑(如配置文件加载),可能因初始化顺序问题导致错误。

4.1.2 懒汉模式(Lazy Initialization)

  • 核心特点首次使用时才创建实例,延迟到需要时再初始化。

  • 实现示例(C++11 线程安全版):

    class Singleton {
    private:// 私有构造函数Singleton() {}// 禁用拷贝和赋值Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 首次调用时创建实例(静态局部变量保证线程安全)static Singleton& getInstance() {static Singleton instance;return instance;}
    };
    
  • 优缺点:

    • 优点:
      • 按需创建,节省资源(未使用时不占用内存)。
      • 初始化顺序灵活,可依赖其他已初始化的资源。
    • 缺点:
      • 实现相对复杂,需要处理线程安全问题(C++11 前需手动加锁,如双检锁)。
      • 首次调用getInstance()时可能有性能开销(初始化耗时)。

核心区别对比

维度饿汉模式懒汉模式
实例创建时机类加载 / 程序启动时首次调用getInstance()
线程安全(天然)是(初始化早于多线程)否(需额外处理,C++11 后改善)
资源效率较低(提前占用资源)较高(按需分配)
实现复杂度简单(无需处理并发)较复杂(需考虑线程安全)
适用场景单例体积小、初始化快单例体积大、初始化耗资源

总结

  • 饿汉模式:“饿” 意味着迫不及待,适合简单、轻量的单例,追求实现简单和线程安全。
  • 懒汉模式:“懒” 意味着延迟行动,适合复杂、耗资源的单例,追求资源利用效率。

实际开发中,若单例初始化成本低且肯定会被使用,优先选饿汉模式;若单例可能不被使用或初始化成本高,选懒汉模式(C++11 及以上推荐静态局部变量方式,简洁且线程安全)。

4.2 类中的成员变量啥时候初始化?

类的成员变量初始化时机取决于变量的类型(如普通成员变量、静态成员变量、常量成员变量等)和初始化方式(如默认初始化、显式初始化、构造函数初始化等)。以下是不同场景下的初始化时机总结:

4.2.1 普通成员变量(非静态、非 const)

普通成员变量属于类的实例,其初始化时机与对象的创建绑定,具体分为两种情况:

  1. 默认初始化(编译器自动处理)
    若未显式初始化,编译器会在对象创建时(即构造函数执行期间)对成员变量进行默认初始化

    • 对于基本数据类型(如intdouble):默认值不确定(局部对象中为随机值,全局 / 静态对象中为 0)。
    • 对于类类型(如string、自定义类):会调用其默认构造函数初始化。

    示例:

    class MyClass {
    private:int a;         // 基本类型,默认初始化值不确定(局部对象中)string str;    // 类类型,默认调用string()构造函数初始化
    };
    
  2. 显式初始化(推荐)
    为避免默认初始化的不确定性,通常需要显式初始化,时机包括:

    • 构造函数初始化列表:在构造函数执行前完成初始化(效率更高,推荐用于所有成员变量)。

      class MyClass {
      private:int a;string str;
      public:// 初始化列表在构造函数体执行前初始化成员变量MyClass() : a(0), str("default") {} 
      };
      
    • 构造函数体内赋值:在构造函数体执行时对已默认初始化的成员变量重新赋值(效率略低,适合复杂逻辑)。

      MyClass() {a = 0;       // 先默认初始化a,再赋值str = "default"; // 先默认初始化str,再赋值
      }
      
    • C++11 后:类内初始值:在成员变量声明时直接赋值(编译器会将其放入初始化列表)。

      class MyClass {
      private:int a = 0;          // 类内初始值string str = "default";
      };
      

4.2.2 静态成员变量(static

静态成员变量属于类本身(而非实例),其初始化时机与类的生命周期绑定,与对象创建无关:

  1. 初始化时机

    • 类外单独初始化,且只初始化一次(程序启动时,在main函数执行前完成)。
    • 若未显式初始化,基本类型默认值为 0,类类型调用默认构造函数。
  2. 注意事项

    • 静态成员变量必须在类外定义(初始化),类内仅声明。
    • 局部静态成员变量(如在函数内定义的static变量)首次调用函数时初始化,且只初始化一次。

    示例:

    class MyClass {
    private:static int count; // 类内声明
    };
    int MyClass::count = 0; // 类外初始化(程序启动时执行)
    

4.2.3 常量成员变量(const

const成员变量必须在初始化时赋值,且赋值后不可修改,初始化时机严格限制:

  1. 普通const成员变量
    必须在构造函数初始化列表中初始化(不能在构造函数体内赋值,因为进入函数体时变量已初始化)。

    示例:

    class MyClass {
    private:const int num;
    public:MyClass(int n) : num(n) {} // 必须在初始化列表中赋值
    };
    
  2. 静态const成员变量

    • 可在类内声明时直接初始化(仅允许基本数据类型或枚举),也可在类外初始化。
    • 若为类类型(如string),必须在类外初始化。

    示例:

    class MyClass {
    private:static const int MAX_SIZE = 100; // 类内初始化(基本类型)static const string NAME;       // 类内声明,类外初始化
    };
    const string MyClass::NAME = "MyClass"; // 类外初始化(类类型)
    

4.2.4 引用成员变量(&

引用必须绑定到一个对象,且一旦绑定不可更改,因此必须在构造函数初始化列表中初始化,与const成员变量类似。

class MyClass {
private:int& ref;
public:MyClass(int& x) : ref(x) {} // 必须在初始化列表中绑定引用
};

4.2.5 总结:核心原则

  1. 普通成员变量:在对象创建时初始化,推荐用构造函数初始化列表或类内初始值。
  2. 静态成员变量:在类外初始化(程序启动时),与对象无关,仅初始化一次。
  3. const/ 引用成员变量:必须在构造函数初始化列表中初始化(静态const基本类型可类内初始化)。

遵循这些规则可避免未初始化的变量导致的未定义行为,确保程序正确性。

五、整体答案

最推荐写法:

#include <iostream>
using namespace std;
class Singleton
{
public:static Singleton* getInstance(){static Singleton* instance;return instance;}
private:Singleton() = default;Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton& other) = delete;
};int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();// 验证是否为同一个实例cout << (s1 == s2 ? "是单例" : "不是单例") << endl; // 输出:是单例
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/92595.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/92595.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/92595.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI开源大模型gpt-oss系列深度解析:从120B生产级到20B桌面级应用指南

引言&#xff1a;OpenAI开源里程碑&#xff0c;AI民主化加速到来 2025年8月&#xff0c;OpenAI正式宣布开源其两款重磅大语言模型——gpt-oss-120b&#xff08;1200亿参数生产级模型&#xff09;和gpt-oss-20b&#xff08;200亿参数桌面级模型&#xff09;&#xff0c;引发全球…

本地部署文档管理平台 BookStack 并实现外部访问( Windows 版本)

BookStack 是一款专注于书籍、文档管理的开源平台&#xff0c;它界面设计直观简洁&#xff0c;功能强大且易于使用&#xff0c;允许用户创建、组织和分享文档资料&#xff0c;特别适合用于构建内部文档系统、知识库或公开的文档站点。本文将详细介绍如何在 Windows 系统本地部署…

VS Code编辑器

实际上&#xff0c;‌Visual Studio Code&#xff08;简称VS Code&#xff09;‌是由微软开发的免费、开源、跨平台的代码编辑器&#xff0c;支持多种编程语言和框架&#xff0c;广泛应用于现代Web和云应用开发。这也是个编辑器&#xff0c;可能是继 GitHub 的 Atom 之后的一枝…

自动化测试篇--BUG篇

目录 一.软件测试的生命周期 二.bug是什么&#xff1f; 三.如何描述一个bug&#xff1f; 四.bug的级别 五.bug的生命周期 六.测试与开发产生争执怎么办&#xff1f;&#xff08;重要&#xff01;&#xff01;&#xff01;&#xff09; 一.软件测试的生命周期 软件测试人员…

Solidity智能合约基础

基础学习使用 remix&#xff1a;ide Remix - Ethereum IDE evm&#xff1a;ethreum virtual machine evm字节码 强类型脚本语言 compile >evm bytescode >evm hello的样例 声明的关键字&#xff1a;contract // SPDX-License-Identifier: MIT pragma solidi…

Unity跨平台超低延迟的RTSP/RTMP播放器技术解析与实战应用

✳️ 引言&#xff1a;为什么说 Unity 中的视频能力是“可视化神经元”&#xff1f; 随着“可视化 实时性”成为工业数字化的关键支撑&#xff0c;Unity 正从传统游戏引擎&#xff0c;演进为数字孪生系统、智能机器人中控、虚拟交互平台、XR 可视引擎等领域的底层核心。它不再…

python学智能算法(三十三)|SVM-构建软边界拉格朗日方程

【1】引用 在前序学习进程中&#xff0c;我们初步了解了SVM软边界&#xff0c;今天就更进一步&#xff0c;尝试构建SVM软边界的拉格朗日函数。 【2】基本问题 在SVM软边界中&#xff0c;我们已经获得此时的最优化几何距离的表达式&#xff1a; fmin⁡12∣∣w∣∣2C∑i1nξif…

【YOLOv5】

Focus模块&#xff1a;早期再yolov5版本提出&#xff0c;后期被常规卷积替换&#xff0c;作用是图像进入主干网络之前&#xff0c;进行隔行隔列采样&#xff0c;把空间维度堆叠到通道上&#xff0c;减少计算量。 SPPF:SPP的改进版本&#xff0c;把SPP的不同池化核改变为K 5 的…

Pytest项目_day05(requests加入headers)

headers 由于每个请求都需要加入一些固定的参数&#xff0c;例如&#xff1a;cookies、user-agent&#xff0c;那么将这些固定参数放入URL或params中会显得很臃肿&#xff0c;因此一般将这些参数放在request headers中headers的反爬作用 在豆瓣网站中&#xff0c;如果我们不加入…

安全引导功能及ATF的启动过程(四)

安全引导功能及ATF的启动过程&#xff08;四&#xff09; ATF中bl31的启动 在bl2中触发安全监控模式调用后会跳转到bl31中执行&#xff0c;bl31最主要的作用是建立EL3运行态的软件配置&#xff0c;在该阶段会完成各种类型的安全监控模式调用ID的注册和对应的ARM核状态的切换&am…

从手工到智能决策,ERP让制造外贸企业告别“数据孤岛“降本增效

在全球化竞争加剧的当下&#xff0c;制造型外贸企业正面临订单碎片化、供应链复杂化、合规风险上升等多重挑战。数字化转型已成为企业突破增长瓶颈、构建核心竞争力的必选项。然而&#xff0c;许多企业在推进过程中因选型不当陷入“系统孤岛”“数据失真”“流程低效”等困境。…

DMETL简单介绍、安装部署和入门尝试

一、DMETL的介绍1.1 概述我们先来简单了解一下DMETL。DMETL是什么&#xff1f;说的简单一点&#xff0c;DMETL一款数据处理与集成平台&#xff1b;从功能来说&#xff0c;那DMETL就是对数据同步、数据处理以及数据交换共享提供一站式支持的平台&#xff1b;从它的意义来说&…

NLP 人工智能 Seq2Seq、K-means应用实践

基于Java和人工智能的Web应用 以下是基于Java和人工智能的Web应用实例,涵盖自然语言处理、计算机视觉、数据分析等领域。这些案例结合了沈七星AI或其他开源框架(如TensorFlow、Deeplearning4j)的实现思路,供开发参考: 自然语言处理(NLP) 1. 智能客服系统 使用Java的Op…

Docker 从入门到实战(一):全面解析容器化革命 | 2025 终极指南

2025 年,全球容器市场规模突破 200 亿美元,超过 80% 的企业生产环境运行在容器之上。掌握 Docker 已成为开发、运维乃至架构师的核心竞争力。本文带你彻底搞懂 Docker 的底层逻辑与核心价值! 一、Docker 是什么?为什么它能改变世界? 想象一下:你开发时运行完美的 Pytho…

Lazada东南亚矩阵营销破局:指纹手机如何以“批量智控+数据中枢”重构运营生态

在Lazada以“超级APP”战略渗透东南亚6国市场的进程中&#xff0c;商家正陷入一个结构性矛盾&#xff1a;如何用有限人力高效管理10个国家账号&#xff0c;却不被数据孤岛拖垮营销效率&#xff0c;更不因账号关联风险引发平台封禁&#xff1f;传统多账号运营依赖“人手一台设备…

操作系统: 线程(Thread)

目录 什么是线程&#xff08;Thread&#xff09;&#xff1f; 线程与进程之间的关系 线程调度与并发执行 并发&#xff08;Concurrency&#xff09;与并行&#xff08;Parallelism&#xff09; 多线程编程的四大核心优势&#xff08;benefits of multithreaded programmin…

Uber的MySQL实践(一)——学习笔记

MySQL 是Uber数据基础设施的核心支柱&#xff0c;支撑着平台上大量关键操作。Uber 拥有一套庞大的 MySQL 集群&#xff0c;如何构建一个控制平面来管理如此大规模的 MySQL 集群&#xff0c;并同时确保零宕机、零数据丢失是一个十分有挑战性的问题。下面重点介绍 Uber 的 MySQL …

腾讯云EdgeOne产品深度分析报告

一、产品概述腾讯云EdgeOne是腾讯云推出的新一代边缘安全加速平台&#xff0c;集成内容分发网络&#xff08;CDN&#xff09;、Web应用防火墙&#xff08;WAF&#xff09;、DDoS防护、Bot管理、API安全及边缘计算能力&#xff0c;致力于为企业提供一站式安全加速解决方案。该平…

Spring Boot 优雅配置InfluxDB3客户端指南:@Configuration + @Bean + yml实战

前言 想用Java玩转InfluxDB 3?要是还靠写main函数硬编码配置,那就像穿着睡衣开正式会议,实在有点不靠谱。现代Spring开发套路讲究配置和代码分离,讲究优雅和灵活。用@Configuration配合@Bean注解,再加上yml配置文件集中管理连接信息,简直是为代码打扮一身西装,既整洁又…

记录:rk3568适配开源GPU驱动(panfrost)

rk3568采用的GPU是Mali-G52&#xff0c;该型号的GPU已在5.10内核的panfrost驱动中被支持。下面记录下移植过程。 1.内核dts修改&#xff1a; kernel 5.10: arch/arm64/boot/dts/rockchip/rk3568.dtsigpu: gpufde60000 {compatible "rockchip,rk3568-mali", "ar…