1 什么是单例模式

单例模式 是一种创建型设计模式,确保一个类在整个程序生命周期中只有一个实例,并提供一个全局访问点。

核心要求:

  1. 类不能被外部随意创建(禁止 public 构造函数或限制实例数量)。
  2. 不能被复制或移动。
  3. 提供一个全局访问接口(如 getInstance())。
  4. 实例的生命周期通常伴随整个程序。

2 模版单例的实现

2.1 单例模版GSingleton<T>

  • 使用场景:项目中需要大量单例,且希望统一管理时,使用单例模版会提高代码复用。单例模版类本身不能直接使用,而是作为基类被继承。需要实现单例的类只需要继承GSingleton即可。

  • 注意:模版基类GSingleton的构造函数和析构函数必须是protected修饰,不能用private,因为派生类在构造时或者析构时可以调用。而派生类T的构造函数和析构函数可以由private修饰。

template<typename T>
class GSingleton
{
protected:GSingleton() = default;~GSingleton() = default;  // 非虚,除非你真的需要// 禁止拷贝和移动GSingleton(const GSingleton&) = delete;GSingleton& operator=(const GSingleton&) = delete;GSingleton(GSingleton&&) = delete;GSingleton& operator=(GSingleton&&) = delete;public:static T& getInstance() {static T instance;return instance;}
};
  • 模版类详细介绍
  1. protect:修饰的方法或者成员变量只能被本类、派生类以及友元访问。
  2. Gsingleton<T>是基类,必须是protected,这样派生类在构造时才可以调用父类的构造函数。
  3. GSingleton = default:表示显式要求编译器生成一个默认构造函数(无参构造函数)
  4. ~GSingleton() = default:表示让编译器生成默认析构函数。
  5. GSingleton(const GSingleton&) = delete:这是对拷贝构造函数的声明,禁止拷贝。
    const GSingleton&:表示对另一个GSingleton对象的常量引用。
    delete表示这个函数被显示删除,不能使用。
    Access a;
    Access b(a);        // ❌ 错误!调用拷贝构造函数
    Access c = a;       // ❌ 错误!也是拷贝构造(赋值语法,实际是构造)
    
  6. GSingleton& operator=(const GSingleton&) = delete:这是对拷贝赋值运算符的声明,禁止拷贝。
    operator=:表示赋值操作符的重载。
    Access a;
    Access b;
    b = a;              // ❌ 错误!调用拷贝赋值
  7. GSingleton(GSingleton&&) = delete:这是移动构造函数的声明,禁止移动构造。
    GSingleton&&:是一个右值引用,表示临时对象或者可移动的对象。
    移动不是拷贝,而是资源转移,将一个临时对象所拥有的资源转移给另一个对象,而原对象不再拥有该资源。
    移动避免了不必要的内存分配和数据赋值,因此非常高效。
    Access a;
    Access b(std::move(a));  // ❌ 错误!试图移动构造(即使 NRVO 优化,语义上也不允许)
  8. GSingleton& operator=(GSingleton&&) = delete:这是移动赋值运算符,禁止将一个临时对象移动赋值给已有对象。
    Access a;
    Access b;
    b = std::move(a);   // ❌ 错误!禁止移动赋值

✅ 总结

代码禁止的操作
Access(const Access&) = delete;Access b(a); 或 Access b = a;
Access& operator=(const Access&) = delete;b = a;
Access(Access&&) = delete;Access b(std::move(a));
Access& operator=(Access&&) = delete;b = std::move(a);

2.2 使用单例模版定义派生类

class GLoggerImpl : public GSingleton<GLoggerImpl>
{
private:GLoggerImpl();friend class GSingleton<GLoggerImpl>;
public:~GLoggerImpl();void WriteLog(int logLevel, const char* file, int line, const char* function, const char* format, va_list args);
}

注意:在声明GloggerImpl的类时使用了friend class GSingleton<GLoggerImpl>,表示声明让模版类成为友元。

原因:在调用GLoggerImpl::getInstance()时,

// template<typename T> class GSingleton 
static T& getInstance() {static T instance;  // ← 这里要构造 T,即 GLoggerImplreturn instance;
}

static T instance会调用GLoggerImpl的构造函数,由于GLoggerImpl()是private或者protected,而GSingleton<GLoggerImpl>模版类不是GLoggerImpl类的成员变量,默认无法访问GLoggerImpl的构造函数,因此要声明friend来显示授权。

友元:是C++的一个关键字,它允许一个类、函数或者模版访问另一个类的private和protected成员,即使它不是那个类的成员函数或者派生类。

  • friend可以修饰函数,称为友元函数,可以在一个类中声明友元函数,这样该函数可以访问该类的私有成员变量或者成员函数。
  • friend可以修饰类,成为友元类,可以访问所在类中的private或者protect的成员,友元关系是单向的。
  • friend可以修饰一个模版类或者模版函数,成为友元模版。例如GloggerImpl中friend修饰的模版。

2.3 单例类的使用

由于GLoggerImpl继承了GSingleton的模版基类,因此继承了父类静态的getInstance()方法。所以通过GLoggerImpl::getInstance()可以获取该类唯一的单例对象。

void LogD(const char* file, int line, const char* function, const char* format, ...) {va_list args;va_start(args, format);GLoggerImpl::getInstance()->WriteLog(LEVEL_DEBUG, file, line, function, format, args);va_end(args);
}

3 单例类的实现

使用场景:项目里只需要少数单例,且追求高可读性和维护性,推荐单例类的实现。

该实现是线程安全的,使用静态局部变量方式实现。

class Singleton {
public:static Singleton& getInstance() {static Singleton instance;  // C++11 起,线程安全return instance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton&&) = delete;Singleton& operator=(Singleton&&) = delete;private:Singleton() = default;~Singleton() = default;
};

使用:Singleton s = Singleton::getInstance()就获取该类的唯一单例。

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

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

相关文章

汇编基础1

1.格式伪操作&#xff1a;它们不是ARM处理器实际的指令&#xff08;如MOV&#xff0c; ADD等&#xff09;&#xff0c;而是写给汇编器看的命令&#xff0c;用于指导汇编器如何工作area reset, code, readonlycode32entry内容 endarea: 这是最重要的一个伪操作&#xff0c;用…

设计模式(C++)详解—单例模式(2)

<摘要> 单例模式是创建型设计模式中最简单但应用最广泛的模式之一&#xff0c;它确保一个类只有一个实例并提供全局访问点。本文从历史背景和核心概念出发&#xff0c;系统阐述了单例模式的产生缘由和演进脉络&#xff0c;深入剖析了其在资源管理、状态一致性和访问控制方…

kafka如何保证消息的顺序性

kafka如何保证消息的顺序性 Kafka只能在分区&#xff08;Partition&#xff09;级别保证消息的顺序性&#xff0c;而不能在主题&#xff08;Topic&#xff09;级别保证全局顺序。 核心原理&#xff1a;分区和偏移量分区&#xff08;Partition&#xff09;是顺序性的基础&#x…

传输层:UDP/TCP协议

网络协议图 一.UDP 特点: 无连接&#xff0c;不可靠&#xff0c;面向数据报&#xff0c;全双工&#xff08;前面网络编程中介绍过&#xff09; 格式: 服务器的端口号一般都是程序员指定的(这样你才能访问到),客户端的端口号是系统自动分配的(如果提前指定好, 可能会与其他程…

A/B测试全解析:原理、流程与实战案例

A/B测试&#xff08;AB Testing&#xff09;原理与实践全解析 在数据驱动的时代&#xff0c;A/B测试几乎是每一个互联网公司都会使用的实验方法。无论是电商平台优化转化率&#xff0c;还是内容平台提升点击率&#xff0c;抑或是游戏公司提升留存&#xff0c;A/B测试都是最常见…

循环神经网络(三):小练习

RNN小练习 要求&#xff1a; 假设有 4 个字 吃 了 没 &#xff1f;&#xff0c;请使用 torch.nn.RNN 完成以下任务 将每个进行 one-hot 编码请使用 吃 了 没 作为输入序列&#xff0c;了 没 &#xff1f; 作为输出序列RNN 的 hidden_size 64请将 RNN 的输出使用全连接转换成 4…

ESPIDF官方文档,启用dhcp会禁用对应的STA或AP的静态IP,我测试STA确实是,但是AP不是,为什么

1. STA 模式下的 DHCP&#xff08;客户端角色&#xff09;ESP32 当 Station&#xff08;STA&#xff09; 时&#xff0c;它的行为就跟你的手机/笔记本连 Wi-Fi 一样&#xff1a;DHCP 客户端 → 去路由器&#xff08;DHCP 服务器&#xff09;要一个 IP。特点启用 DHCP&#xff0…

cocos2d. 3.17.2 c++如何实现下载断点续传zip压缩包带进度条

新建类CurlDown #include “curl/curl.h” #include using namespace std; USING_NS_CC; /** 资源下载curl */ class CurlDown { public: CurlDown(); ~CurlDown(); void StartDownResZip(string downLoadUrl, int64_t totalSize); //下载控制 void downloadControler(); //下…

MySQL 整型数据类型:选对数字类型,让存储效率翻倍

MySQL 整型数据类型&#xff1a;选对数字类型&#xff0c;让存储效率翻倍 在 MySQL 中&#xff0c;整型&#xff08;整数类型&#xff09;是最常用的数据类型之一&#xff0c;从用户 ID 到商品数量&#xff0c;几乎所有涉及数字的场景都离不开它。但你知道吗&#xff1f;选对整…

公司电脑监控软件有哪些?公司电脑监控软件应该怎么选择

大家好呀&#xff0c;电竞直播运营团队常常面临 “直播脚本被抄袭、用户付费数据篡改、主播话术外泄” 的问题&#xff01;尤其是独家直播流程脚本、用户充值记录、主播互动话术库、赛事解说手稿&#xff0c;一旦泄露可能导致竞品跟风、用户信任下降、直播竞争力减弱&#xff5…

ARM裸机开发:链接脚本、进阶Makefile(bsp)、编译过程、beep实验

一、链接脚本的作用&#xff1f;各个段存放什么数据类型&#xff08;一&#xff09;链接脚本内容SECTIONS {. 0x87800000;.text : {obj/start.o*(.text)}.rodata ALIGN(4) : {*(.rodata*)}.data ALIGN(4) : {*(.data)}__bss_start .;.bss ALIGN(4) : {*(.bss) *(COMMON)}__bs…

Linux驱动开发(1)概念、环境与代码框架

一、驱动概念驱动与底层硬件直接打交道&#xff0c;充当了硬件与应用软件中间的桥梁。1、具体任务&#xff08;1&#xff09;读写设备寄存器&#xff08;实现控制的方式&#xff09;&#xff08;2&#xff09;完成设备的轮询、中断处理、DMA通信&#xff08;CPU与外设通信的方式…

计算机视觉(十):ROI

什么是感兴趣区域&#xff08;ROI&#xff09;&#xff1f; 在计算机视觉中&#xff0c;**感兴趣区域&#xff08;ROI&#xff09;**指的是图像中包含我们想要分析、处理或识别的目标或特征的特定子集。就像我们在阅读一本书时会聚焦于某个重要的段落&#xff0c;计算机视觉系统…

Jenkins 构建 Node 项目报错解析与解决——pnpm lockfile 问题实战

在使用 Jenkins 自动化构建 Node.js 项目时&#xff0c;经常会遇到类似报错&#xff1a; ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with package.json Error: Cannot find module node_module…

Kafka在多环境中安全管理敏感

1. 配置提供者是什么&#xff1f; 配置提供者&#xff08;ConfigProvider&#xff09;是一类按需“拉取配置”的组件&#xff1a;应用读取配置时&#xff0c;按约定的占位符语法去外部来源&#xff08;目录、环境变量、单一 properties 文件、你自定义的来源……&#xff09;取…

编程工具的演进逻辑:从Python IDLE到Arduino IDE的深度剖析

引言:工具进化的本质 在编程学习与开发的道路上,我们总会与各种各样的工具相遇。一个有趣的现象是,无论是初学者的第一款工具Python IDLE,还是硬件爱好者常用的Thonny和Arduino IDE,它们都自称“集成开发环境”(IDE)。这背后隐藏着怎样的逻辑? 本文将带你深入分析这三…

p10k configure执行报错: ~/powerlevel10k/config/p10k-lean.zsh is not readable

[ERROR] p10k configure: ~/powerlevel10k/config/p10k-lean.zsh is not readable 背景 我移动了Powerlevel10k文件夹的位置&#xff0c;导致p10k configure命令找不到powerlevel10k文件夹的位置。 原来Powerlevel10k的位置&#xff1a;~/powerlevel10k 移动后Powerlevel10k的位…

Java 学习笔记(进阶篇3)

1. 美化界面关键逻辑 1&#xff1a;// 相对路径&#xff1a;直接从项目的 src 目录开始写&#xff0c;不包含 D:\ 和个人名字 ImageIcon bg new ImageIcon("src/image/background.png"); JLabel background new JLabel(bg);这两行代码是 Swing 中加载并显示图片的经…

BFD 概述

BFD简介1.BFD:Bidirectional Forwarding Detection,双向转发检查概述&#xff1a;毫秒级链路故障检查&#xff0c;通常结合三层协议&#xff08;如静态路由、vrrp、 ospf、 BGP等&#xff09;实现链路故障快速切换。作用&#xff1a;① 检测二层非直连故障② 加快三层协议收敛底…

【嵌入式DIY实例-ESP32篇】-Flappy Bird游戏

Flappy Bird游戏 文章目录 Flappy Bird游戏 1、游戏介绍 2、硬件准备与接线 3、代码实现 《Flappy Bird》游戏以其引人入胜的玩法和简约的设计风靡全球。本文将探讨如何使用 OLED SSD1306 显示屏和 ESP32 微控制器重现这款经典游戏。这个 DIY 项目不仅充满乐趣,也是学习编程和…