C++设计模式之单例模式(Singleton)

news2024/11/25 13:43:34

文章目录

    • 定义
    • 前言
      • 1. 问题
      • 2. 解决方案
    • 适用场景
    • 实现方式
      • 优点
      • 缺点
      • 与其他模式的关系
      • 懒汉单例模式代码
        • 1. 线程不安全的懒汉单例模式
        • 2. 线程安全的懒汉单例模式
      • 饿汉单例模式代码
      • Meyers' Singleton
        • 优点:
        • 缺点:

定义

单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。

前言

1. 问题

单例模式同时解决了两个问题,所以违反了单一职责原则:

  1. 保证一个类只有一个实例。
  2. 为该实例提供一个全局访问节点。

为什么会有人想要控制一个类所拥有的实例数量?最常见的原因是控制某些共享资源(例如数据库或文件)的访问权限。它的运作方式是这样的:如果你创建了一个对象,同时过一会儿后你决定再创建一个新对象,此时你会获得之前已创建的对象,而不是一个新对象。

注意,普通构造函数无法实现上述行为,因为构造函数的设计决定了它必须总是返回一个新对象。

在这里插入图片描述

还记得你用过的那些存储重要对象的全局变量吗?它们在使用上十分方便,但同时也非常不安全,因为任何代码都有可能覆盖掉那些变量的内容,从而引发程序崩溃。和全局变量一样,单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。

还有一点:你不会希望解决同一个问题的代码分散在程序各处的。因此更好的方式是将其放在同一个类中,特别是当其他代码已经依赖这个类时更应该如此。

如今,单例模式已经变得非常流行,以至于人们会将只解决上文描述中任意一个问题的东西称为单例。

2. 解决方案

所有单例的实现都包含以下两个相同的步骤:

● 将默认构造函数设为私有, 防止其他对象使用单例类的new运算符。
● 新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类,那它就能调用单例类的静态方法。无论何时调用该方法,它总是会返回相同的对象。

结构

在这里插入图片描述

单例(Singleton) 类声明了一个名为getInstance 获取实例的静态方法来返回其所属类的一个相同实例。

单例的构造函数必须对客户端(Client) 代码隐藏。调用获取实例方法必须是获取单例对象的唯一方式。

适用场景

● 如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。

单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。该方法可以创建一个新对象,但如果该对象已经被创建,则返回已有的对象。

● 如果你需要更加严格地控制全局变量,可以使用单例模式。

单例模式与全局变量不同,它保证类只存在一个实例。除了单例类自己以外,无法通过任何方式替换缓存的实例。请注意, 你可以随时调整限制并设定生成单例实例的数量,只需修改获取实例方法, 即getInstance 中的代码即可实现。

实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建方法用于获取单例实例。
  3. 在静态方法中实现"延迟初始化"。该方法会在首次被调用时创建一个新对象,并将其存储在静态成员变量中。此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用。
  5. 检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。

优点

● 你可以保证一个类只有一个实例。
● 你获得了一个指向该实例的全局访问节点。
● 仅在首次请求单例对象时对其进行初始化。

缺点

● 违反了单一职责原则。该模式同时解决了两个问题。
● 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
● 该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。
● 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以你需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代码,或者不使用单例模式。

与其他模式的关系

● 外观类通常可以转换为单例类,因为在大部分情况下一个外观对象就足够了。
● 如果你能将对象的所有共享状态简化为一个享元对象,那么享元就和单例类似了。但这两个模式有两个根本性的不同。
○ 只会有一个单例实体,但是享元类可以有多个实体,各实体的内在状态也可以不同。
○ 单例对象可以是可变的。享元对象是不可变的。
● 抽象工厂、生成器和原型都可以用单例来实现。

懒汉单例模式代码

1. 线程不安全的懒汉单例模式

注意懒汉模式在不加锁情况下是线程不安全的。

Singleton.h:

● 构造函数私有:即单例模式只能在内部私有化
● 实例对象static:保证全局只有一个
● 外界通过GetInstance()获取实例对象

#ifndef SINGLETON_H_
#define SINGLETON_H_

#include <iostream>
#include <string>

class Singleton {
 public:
    static Singleton* GetInstance() {
        if (instance_ == nullptr) {
            instance_ = new Singleton();
        }
        return instance_;
    }
 private:
    Singleton() {}
    static Singleton* instance_;
};

#endif  // SINGLETON_H_

Singleton.cpp:

#include "Singleton.h"

// 静态变量instance初始化不要放在头文件中, 如果多个文件包含singleton.h会出现重复定义问题
Singleton* Singleton::instance_ = nullptr;

main.cpp:

#include <iostream>
#include "Singleton.h"

int main() {
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

    std::cout << "s1地址: " << s1 << std::endl;
    std::cout << "s2地址: " << s2 << std::endl;
    return 0;
}

编译运行:

$g++ -g main.cpp Singleton.cpp -std=c++11 -o singleton
$./singleton 
s1地址: 0x95a040
s2地址: 0x95a040

2. 线程安全的懒汉单例模式

上述代码并不是线程安全的,当多个线程同时调用Singleton::GetInstance(),可能会创建多个实例从而导致内存泄漏(会new多次但我们只能管理唯一的一个instance_),我们这里简单通过互斥锁实现线程安全。

Singleton.h:

#ifndef SINGLETON_H_
#define SINGLETON_H_

#include <iostream>
#include <string>
#include <mutex>

class Singleton {
 public:
    static Singleton* GetInstance() {
        if (instance_ == nullptr) {
            // 加锁保证多个线程并发调用getInstance()时只会创建一个实例
            m_mutex_.lock();
            if (instance_ == nullptr) {
                instance_ = new Singleton();
            }
            m_mutex_.unlock();
        }
        return instance_;
    }
 private:
    Singleton() {}
    static Singleton* instance_;
    static std::mutex m_mutex_;
};

#endif  // SINGLETON_H_

Singleton.cpp:

#include "Singleton.h"

// 静态变量instance初始化不要放在头文件中, 如果多个文件包含singleton.h会出现重复定义问题
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::m_mutex_;

main.cpp:

#include <iostream>
#include "Singleton.h"

int main() {
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

    std::cout << "s1地址: " << s1 << std::endl;
    std::cout << "s2地址: " << s2 << std::endl;
    return 0;
}

饿汉单例模式代码

Singleton.h:

#ifndef SINGLETON_H_
#define SINGLETON_H_

class Singleton {
 public:
    static Singleton* GetInstance() {
        return instance_;
    }

 private:
    Singleton() {}
    static Singleton* instance_;
};

#endif  // SINGLETON_H_

Singleton.cpp:

#include "Singleton.h"

Singleton* Singleton::instance_ = new Singleton();

main.cpp:

#include <iostream>
#include "Singleton.h"

int main() {
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

    std::cout << "s1地址: " << s1 << std::endl;
    std::cout << "s2地址: " << s2 << std::endl;
    return 0;
}

编译运行:

$g++ -g main.cpp Singleton.cpp -std=c++11 -o singleton
$./singleton 
s1地址: 0x18a8040
s2地址: 0x18a8040

Meyers’ Singleton

Meyers’ Singleton是Scott Meyers提出的C++单例的推荐写法。它将单例对象作为局部static对象定义在函数内部:

#ifndef SINGLETON_H_
#define SINGLETON_H_

class Singleton {
 public:
    static Singleton& GetInstance() {
        static Singleton instance;
        return instance;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

 private:
    Singleton() {}
};

#endif  // SINGLETON_H_

优点:

● 解决了普通单例模式全局变量初始化依赖(C++只能保证在同一个文件中声明的static遍历初始化顺序和其遍历声明的顺序一致,但是不能保证不同文件中static遍历的初始化顺序)

缺点:

● 需要C11支持(C11保证static成员初始化的线程安全)
● 性能问题(同懒汉模式一样,每次调用GetInstance()方法时需要判断局部static变量是否已经初始化,如果没有初始化就会进行初始化,这个判断逻辑会消耗一点性能)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从“五个女博士”事件,论品牌广告营销的正确姿势

【潮汐商业评论/原创】 “她经济”是一片浩浩荡荡又风云变幻的海&#xff0c;“美”则是其中贯穿女性一生的课题&#xff0c;如果说这两年这个课题有什么变化&#xff0c;那就是从“女为悦己者容”到“女为自己容”&#xff0c;女性追求美的出发点早就不再是取悦他人“服美役”…

从外资企业数字化趋势看MNC根植中国的新逻辑

导读&#xff1a;外企数字化发展&#xff0c;也要“在中国&#xff0c;为中国” 外资企业一直是中国经济发展的重要推动力量。根据商务部数据&#xff0c;2022年中国实际使用外商直接投资金额达到1891亿美元&#xff0c;创历史新高&#xff0c;同比增速为8%。中国经济强大的韧性…

linux下文件锁使用总结

linux锁存在强制锁&#xff08;mandatory lock&#xff09;和劝告锁&#xff08;advisory lock&#xff09;。所谓强制锁&#xff0c;就是一个进程获取了那把锁&#xff08;只有一把钥匙&#xff09;&#xff0c;只有一个进程可以操作&#xff0c;别的进程无能为力。所谓劝告锁…

【数据库】Mysql索引、事务与存储引擎

文章目录 一、索引介绍1. 索引的概念2. 索引的作用与副作用2.1 索引的作用2.2 索引的副作用2.3 如何实现索引 3. 创建索引的原则依据4. 索引的分类和创建4.1 普通索引直接创建索引修改表方式创建创建表的时指定索引 4.2 唯一索引直接创建唯一索引修改表方式创建创建表的时候指定…

实测Maven依赖包可通过域名抢注实现钓鱼攻击吗

先说结论&#xff1a;基本不可行 原理 Maven包中 groupId 字段是域名反写&#xff0c;比如你有一个 12345.com&#xff0c;就可以申请到 com.12345 的groupId。 很多开源项目都停止维护&#xff0c;但是很多人使用&#xff0c;这些团队可能忘记续费域名&#xff1b;同时目前…

keras运行debug解决protobuf版本冲突

# code 5.4 使用Keras实现异或网络 import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Activation from tensorflow.keras.optimizers import SGD x_train np.array([[0, 0],[0, 1],[1, 0],[1, 1] ]) y_train …

她98年的,我玩不过她...┭┮﹏┭┮

现在的小年轻真的卷得过分了。前段时间我们公司来了个98年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条…

高级VLAN_vlan聚合(Super VLAN)实验

vlan聚合(Super_VLAN) 就是在一个物理网络里面用多个vlan来隔离广播域,将这个vlan聚合成一个逻辑vlan就有了Super vlan,这些多个vlan共用一个ip子网和网关, 目的就是为了节约ip地址资源 普通vlan示意图 普通vlan就存在很多网关,有的vlan可能会存在只有20个主机设备,但是需要划…

罗德FSH13手持式频谱分析仪RohdeSchwarz

FSH4和R&S?FSH8是罗德与施瓦茨公司全新推出面向未来应用的手持式频谱分析仪。它集频谱分 析、天馈线分析、全功能矢量网络分析、矢量电压表、功率计主机、宽带通信解调等多种测试功能 于一身&#xff0c;拥有媲美中高档台式频谱仪的指标。R&S?FSH4和R&S?FSH8完美…

023+limou+C语言的“可变参数列表”和“命令行参数”以及“递归调用”

0.前言 您好&#xff0c;这里是limou3434的一篇博文&#xff0c;感兴趣可以看看我的其他内容。本次我给您带来了C语言的“可变参数列表”&#xff0c;要明白这些内容&#xff0c;您可能需要重新复习下C语言视角的栈帧空间知识。最后我还给出两个小的C语言知识点&#xff1a;“…

Metasploit渗透测试框架

文章目录 Metasploit渗透测试框架基于TCP协议收集主机信息开放端口扫描db_nmap查询网段内在线存活的主机半连接的方式进行半连接扫描使用auxiliary/sniffer下的psunffle模块进行密码嗅探 基于SNMP协议收集主机信息基于SSH协议收集主机信息实战-制作Linux恶意病毒获取公司服务权…

JAVA开发(通过Apollo注入配置信息的几种方式)

前言 在springCloud中有一个重要的组件就是配置中心&#xff0c;config:server&#xff0c;用于配置springboot中需要注入的各种配置项。但是现在发现越来越多的企业使用Apollo进行集成。博主在开发中也是使用Apollo进行配置。本文总结Apollo的的使用&#xff0c;集成到spring…

大模型入门(四)—— 基于peft 微调 LLaMa模型

llama-7b模型大小大约27G&#xff0c;本文在单张/两张 16G V100上基于hugging face的peft库实现了llama-7b的微调。 1、模型和数据准备 使用的大模型&#xff1a;https://huggingface.co/decapoda-research/llama-7b-hf&#xff0c;已经是float16的模型。 微调数据集&#x…

ASEMI代理光宝光耦LTV-0314的应用与优势

编辑-Z 在电子设备的设计和制造过程中&#xff0c;光耦合器是一种至关重要的组件。它们在电路中起到隔离作用&#xff0c;保护电子设备免受电压冲击和电流过载的影响。今天&#xff0c;我们将深入探讨一种特殊的光耦合器——LTV-0314&#xff0c;它的特性、应用以及优势。 一、…

细说如何封装一个日历组件(多视图、可选择、国际化)

前言 最近好奇日历组件是怎么实现的。于是阅读了下react-calendar的源码&#xff0c;并实现了简化版的日历组件。本文把实现日历的设计思路分享给大家。只要理清了主要逻辑&#xff0c;就不难实现了。 技术栈&#xff1a;react、typescript 预览 在线预览demo&#xff1a;c…

亚马逊云科技中国峰会:探索强化学习的未来与Amazon DeepRacer赛车比赛

目录 一、如何构建自己的第一个强化学习模型第一步: 创建 AWS DeepRacer 资源第二步: 定义你的赛道第三步: 训练你的模型第四步: 优化你的模型第五步: 在仿真器中测试你的模型第六步: 在真实赛道上测试你的模型 二、Amazon DeepRacer 中国峰会总决赛三、Amazon DeepRacer 自动驾…

Redis基础+使用+八股文!万字详解一篇就够!

一、目标 学习Redis基础必须掌握的内容&#xff1a; 了解 Redis 以及缓存的作用&#xff1b;掌握 Redis 5 大基本数据类型的使用&#xff1b;掌握常见Redis 面试题&#xff1b;掌握 Redis 的持久化功能&#xff1b;了解 Redis 集群功能。 二、什么是缓存&#xff1f; 缓存定义…

Netty中PileLine类介绍

一、Netty基本介绍 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具&#xff0c;用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty 在保证易于开发的同时还保证了其应用的性能&#xff0c;稳定性和伸缩性。 Netty 是一…

VTK Filter 总结

源对象 成像滤波器 可视化滤波器 可视化滤波器&#xff08;输入类型vtkDataSet&#xff09;。 可视化滤波器&#xff08;输入类型vtkPointSet) 可视化滤波器&#xff08;输入类型vtkPolyData) 可视化滤波器&#xff08;(输入类型vtkStructuredGrid)。 可视化滤波器&#xff08;…

浅析视频监控技术及AI发展趋势下的智能化视频技术应用

视频监控技术是指通过摄像机对指定区域进行实时视频直播、录制、传输、存储、管理和分析的技术系统。它可以用于监控各种场所&#xff0c;如校园、工厂、工地、工作场所、公共区域、交通工具等。视频监控技术主要涉及到以下几个部分&#xff1a; 1、摄像机 摄像机是视频监控技…