23种设计模式(一)——单例模式【对象性能】

news2024/9/30 23:35:56

文章目录

    • 意图
    • 什么时候使用单例
    • 单例模式的实现
      • 1、有缺陷的懒汉式
      • 2、线程安全、内存安全的懒汉式单例 (智能指针,锁)
      • 3、最推荐的懒汉式单例([magic](https://so.csdn.net/so/search?q=magic&spm=1001.2101.3001.7020) static )——局部静态变量
      • 4、单例模式模板类的实现
      • 5、不需要在子类声明友元的实现方法
    • 单例模式的优缺点

亦称: 单件模式、Singleton

意图

单例的特点是只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获取到那个唯一实例。

在这里插入图片描述

什么时候使用单例

具体运用场景如:

  • 设备管理器,系统中可能有多个设备,但是只有一个设备管理器,用于管理设备驱动;

  • 数据池,用来缓存数据的数据结构,需要在一处写,多处读取;

单例模式的实现

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

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

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

1、有缺陷的懒汉式

懒汉式的方法是直到使用时才实例化对象,也就是直到调用GetInstance()方法时才new一个单例的对象。

class Singleton
{
protected:
	Singleton(const std::string value) :value_(value)
	{

	}
	
	static Singleton* singleton_;

	std::string value_;

public:
	Singleton(Singleton &other) = delete;

	void operator=(const Singleton &) = delete;

	static Singleton *GetInstance(const std::string& value);

	void SomeBusinessLogic()
	{
		// ...
	}

	std::string value() const {
		return value_;
	}
};

Singleton* Singleton::singleton_ = nullptr;;

Singleton *Singleton::GetInstance(const std::string& value)
{
	if (singleton_ == nullptr) 
	{
		singleton_ = new Singleton(value);
	}
	return singleton_;
}

这是个最基础版本的实现,存在以下2个问题:

1)线程安全的问题,当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 singleton_是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断singleton_还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来。解决办法:加锁

2)内存泄漏,注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。解决办法: 使用共享指针;

因此,这里提供一个改进的,线程安全的、使用智能指针的实现;

2、线程安全、内存安全的懒汉式单例 (智能指针,锁)

class Singleton
{
public:
    //typedef std::shared_ptr<Singleton> Ptr;
    using Ptr = std::shared_ptr<Singleton>;
 
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
 
    Singleton(Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
 
    static Ptr getInstance()
    {
        // 双重锁
        if(m_pInstance==nullptr){
            std::lock_guard<std::mutex> lk(m_mutex);
 
            if(m_pInstance == nullptr){
                m_pInstance = std::shared_ptr<Singleton>(new Singleton);
            }
        }
 
        return m_pInstance;
    }
 
private:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }
 
private:
    static Ptr m_pInstance;
    static std::mutex m_mutex;
};
 
// initialization static variables out of class
Singleton::Ptr Singleton::m_pInstance = nullptr;
std::mutex Singleton::m_mutex;

shared_ptrmutex都是C++11的标准,以上这种方法的优点是:

1)基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏。

2)加了锁,使用互斥量来达到线程安全。这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,锁的开销毕竟还是有点大的。

不足之处在于: 使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实现上我们希望越简单越好。还有更加严重的问题,在某些平台(与编译器和指令集架构有关),双检锁会失效!

因此这里还有第三种的基于 Magic Static的方法达到线程安全。

3、最推荐的懒汉式单例(magic static )——局部静态变量

class Singleton
{
private:
	Singleton() = default;
	~Singleton() = default;

public:
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;

	static Singleton& GetInstance()
	{
		static Singleton instance;
		return instance;
	}
};

这种方法又叫做 Meyers’ SingletonMeyer’s的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。

这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。

C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。

这是最推荐的一种单例实现方式:通过局部静态变量的特性保证了线程安全 , 不需要使用共享指针,代码简洁;注意在使用的时候需要声明单例的引用 Singleton& 才能获取对象。
另外网上有人的实现返回指针而不是返回引用

static Singleton* get_instance()
{
    static Singleton instance;
    return &instance;
}

这样做并不好,理由主要是无法避免用户使用delete instance导致对象被提前销毁。还是建议大家使用返回引用的方式。

4、单例模式模板类的实现

template<typename T>
class Singleton {
public:
	static T& getInstance() {
		static T instance;
		return instance;
	}

	virtual ~Singleton() {
		std::cout << "destructor called!" << std::endl;
	}

	Singleton(const Singleton&) = delete;
	Singleton& operator =(const Singleton&) = delete;

protected:
	Singleton() {
		std::cout << "constructor called!" << std::endl;
	}
};

/********************************************/
// Example:
// 1.friend class declaration is requiered!
// 2.constructor should be private

class DerivedSingle : public Singleton<DerivedSingle> {
	// !!!! attention!!!
	// needs to be friend in order to
	// access the private constructor/destructor
	friend class Singleton<DerivedSingle>;

public:
	DerivedSingle(const DerivedSingle&) = delete;
	DerivedSingle& operator =(const DerivedSingle&) = delete;

private:
	DerivedSingle() = default;
};

以上实现一个单例的模板基类,使用方法如例子所示意,子类需要将自己作为模板参数T 传递给 Singleton 模板; 同时需要将基类声明为友元,这样才能调用子类的私有构造函数。

基类模板的实现要点是:

  • 构造函数需要是 protected,这样子类才能继承;

  • 使用了奇异递归模板模式CRTP(Curiously recurring template pattern)

在这里基类的析构函数可以不需要 virtual ,因为子类在应用中只会用 Derived 类型,保证了析构时和构造时的类型一致。

5、不需要在子类声明友元的实现方法

在 stackoverflow上, 有大神给出了不需要在子类中声明友元的方法,在这里一并放出;精髓在于使用一个代理类 token,子类构造函数需要传递token类才能构造,但是把 token保护其起来, 然后子类的构造函数就可以是公有的了,这个子类只有 Derived(token)的这样的构造函数,这样用户就无法自己定义一个类的实例了,起到控制其唯一性的作用。

template<typename T>
class Singleton {
public:
	//  C++ STL的std::is_nothrow_constructible模板用于检查给定类型T是否是带有参数集的可构造类型,并且众所周知,它不会引发任何异常。如果T为可构造类型,则返回布尔值true,否则返回false。
	static T& getInstance() noexcept(std::is_nothrow_constructible<T>::value)
	{
		
		static T instance{token()};
		return instance;
	}

	virtual ~Singleton() = default;

	Singleton(const Singleton&) = delete;
	Singleton& operator =(const Singleton&) = delete;

protected:
	struct token {}; // helper class
	Singleton() noexcept = default;
};

/********************************************/
// Example:
// constructor should be public because protected `token` control the access

class DerivedSingle : public Singleton<DerivedSingle> {

public:
	DerivedSingle(token) {
		std::cout << "destructor called!" << std::endl;
	}

	~DerivedSingle() {
		std::cout << "constructor called!" << std::endl;
	}
	DerivedSingle(const DerivedSingle&) = delete;
	DerivedSingle& operator =(const DerivedSingle&) = delete;
};

单例模式的优缺点

优点缺点
保证一个类只有一个实例违反了_单一职责原则_。 该模式同时解决了两个问题。
获得了一个指向该实例的全局访问节点单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
仅在首次请求单例对象时对其进行初始化单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

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

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

相关文章

Wondershare 有哪些不错的办公软件呢

第一款 Wondershare UniConverter14 Wondershare UniConverter中文学习版(万兴优转)是一款国产全能视频格式转换软件.万兴格式转换器最新版具有音视频格式转换,合并视频,视频压缩,视频编辑,视频录制,视频下载,视频元数据修复,VR视频转换,字幕编辑器,GIF制作,DVD刻录等一站式视…

天线参数介绍

1.天线辐射元件种类图1-1 天线辐射元件种类如图1-1所示&#xff0c;天线辐射元件种类有&#xff1a;电镀偶极子、印刷电路偶极子、裂缝波导、凹口辐射器、矩形贴片辐射器、开口波导等。2、天线参数2.1 天线方向图天线方向图又叫辐射方向图&#xff08;radiation pattern&#x…

【MySQL基础教程】多表查询

前言 本文为 【MySQL基础教程】多表查询 相关知识&#xff0c;下边将对多表关系&#xff0c;多表查询概述&#xff0c;内连接&#xff0c;外连接&#xff0c;自连接&#xff0c;子查询&#xff0c;多表查询案例等进行详尽介绍~ &#x1f4cc;博主主页&#xff1a;小新要变强 的…

分布式理论协议与算法 第一弹 CAP理论

CAP 理论&#xff0c;也被称为 CAP 协议&#xff0c;指的是在一个分布式系统中&#xff0c;最多只能同时满足「一致性&#xff08;Consistency&#xff09;」、「可用性&#xff08;Availability&#xff09;」和「分区容错性&#xff08;Partition tolerance&#xff09;」这三…

Vue3 中组合式下的侦听器

目录前言&#xff1a;前置代码&#xff1a;创建侦听器停止侦听器watch侦听&#xff1a;对象中的某一个属性变化(需要提供getter函数才行)watch侦听&#xff1a;整个对象watch侦听&#xff1a;如果嵌套属性发生改变&#xff0c;默认是无法执行回调函数的?watch侦听&#xff1a;…

【实战与杂谈】如何复活一个开源网站-游戏王卡片生成器

1. 杂谈 作为一名十多年游戏王玩家&#xff0c;学生时代玩的是PSP和PS2上的游戏&#xff0c;到毕业后使用YGOPRO同步新卡片进行联网对战&#xff0c;再到现在约到线下进行实体卡片游戏。有些卡片价格太贵&#xff0c;因此我们对于这些卡在未购买之前都会自己打印出来暂时游玩…

sec5-属性

1 属性 GObject系统提供属性。属性是由实例保存的值&#xff0c;实例是GObject的后代&#xff0c;它们对其他实例开放。可以通过他们的名字访问他们。 例如&#xff0c;GtkWindow具有"title"、“default-width”、"default-height"等属性。字符串"t…

正经科普:DDos高防ip详解

白衬衫容易发黄&#xff0c; 一般洗衣液很难洗掉&#xff0c; 不少人为此感到头疼&#xff0c; 不妨在洗的时候吃点头痛药。 这边我也不多废话,大家直接看图,高防ip原理如下清洗能力 DDoS高防IP采用BGP链路对接全国各地30家运营商&#xff0c;总防御能力超4T。采用电信云堤近源…

智能优化算法:人工兔优化算法-附代码

智能优化算法&#xff1a;人工兔优化算法 摘要&#xff1a;人工兔优化算法( [Artificial rabbits optimization&#xff0c;RSO)是 Liying Wang等 于 2022 年提出的一种新型元启发式优化算法 。 该算法受来源于自然界中兔子的生存策略的启发&#xff0c;具有寻优能力强&#x…

【2.1】服务拆分--案例Demo

服务拆分--案例Demo服务拆分注意事项&#xff1a;导入服务拆分Demo测试结果&#xff1a;总结知识内容来自于黑马程序员视频教学和百度百科。博主仅作笔记整理便于回顾学习。如有侵权请私信我。 服务拆分注意事项&#xff1a; 比如现在有一个需求&#xff0c;是查询订单&#x…

国产GPU芯片突破重围,迎来新发展,中国崛起的力量

最近&#xff0c;在GPU芯片领域我们终于迎来新进展&#xff0c;有望突破欧美企业垄断&#xff0c;实现完全国产化。高端GPU芯片对科技发展有着重要作用&#xff0c;广泛应用于云上服务、高密度高性能计算等领域&#xff0c;然而绝大部分GPU芯片市场都被英伟达、AMD、微软等国外…

骨传导耳挂式耳机排名前十名,最好的骨传导耳机推荐

挂耳式的骨传导耳机在佩戴时更舒适以及更加牢固&#xff0c;日常在多种场景使用都能完全兼顾。但是最好的骨传导耳机有哪些呢&#xff1f;还不知道如何选择骨传导耳机&#xff0c;可以看看这五款骨传导耳机~ 1、南卡Runner Pro4骨传导蓝牙耳机 &#xffe5;1498 选择骨传导耳机…

Kubernetes那点事儿——控制器Deployment

K8s应用程序生命周期管理——控制器Deployment一、部署应用程序流程二、Deployment控制器1、应用升级、弹性伸缩、回滚、删除2、滚动升级、回滚机制3、定义Deployment前言说到K8s程序的生命周期管理我们不得不提到k8s的控制器。其中Deployment是最为常用的controllers&#xff…

Liga妙谈 | 如何快速甄别、高效响应用户反馈?

敏捷开发说要「拥抱变化」&#xff0c;在充满不确定的环境中&#xff0c;唯一不变的正是变化。面对源源不断的市场反馈和需求变更&#xff0c;敏捷团队应该如何平衡「高效迭代」与「响应用户」的关系&#xff0c;既快又好地完成研发任务&#xff0c;交付业务价值&#xff1f; …

FFmpeg 滤镜详解

FFmpeg Filter 1. 概念介绍 在多媒体处理中&#xff0c;术语滤镜(filter)指的是修改未编码的原始音视频数据帧的一种软件工具。 2. 基本原理 ● 在编码前&#xff0c;ffmpeg可以对raw&#xff08;真实/原&#xff09;音频和视频使用libavfilter库中的滤镜进行处理。(非压缩…

骨感传导蓝牙耳机怎么样、骨感传导蓝牙耳机有什么特点

在正文开始前&#xff0c;先跟大家说明一下&#xff0c;骨感传导其实就是我们常说的骨传导&#xff0c;两者是相同的意思&#xff0c;只是表达的文字不太一样。我们可以理解为骨感传导耳机骨传导耳机&#xff0c;那既然是这样&#xff0c;骨传导耳机又是利用什么原理传播声音的…

CentOS搭建web服务器,并内网穿透实现公网访问

在web项目中,部署的web站点需要被外部访问,则需要一个媒介,通过把资源放在这个媒介中,再通过所暴露的端口指向这个站点,当外部访问这个媒介所对应的端口时,媒介指向站点,完成访问,像这种类似的媒介,常用的有tomcat容器、Apache等,这边使用Apache来建搭建。 Apache2 是一种流行…

一节摹课丨做会动的电子贺卡,当懂浪漫的成年人

你正在阅读摹客全新内容栏目【一节摹课】。 本栏目会通过一些有趣的、实用的、好看的、新潮的实操案例&#xff0c;分享摹客“设计12”产品矩阵 —— 「1个协作平台2款设计工具」的具体操作妙计&#xff01; 带大家一步一步地解锁摹客协作、摹客RP、摹客DT中的大小功能。 一…

非正式全面解析 NebulaGraph 中 Session 管理

NebulaGraph 论坛最近有些讨论帖&#xff0c;各种姿势来问 NebulaGraph Session 管理相关的事情&#xff0c;我寻思这也不是一个法子&#xff0c;还是来写一篇文章来讲述下 NebulaGraph 中的 Session 管理。由于本文设定为非正式的 Session 讲解&#xff0c;所以本文主要分为理…

智能车|直流电机、编码器与驱动器---编码器

智能车|直流电机、编码器与驱动器---编码器编码器编码器简介编码器的工作原理四倍频采集编码器采集程序实现编码器 编码器简介 编码器是一种将角位移或者直线位移转换成一连串电数字脉冲的一种传感器。 可以通过编码器测量电机转动的位移或者速度信息。 编码器按照工作原理…