【C++】设计模式-单例模式

news/2024/10/23 16:28:40/

目录

一、单例模式

单例模式的三个要点

针对上述三要点的解决方案

常用的两类单例模式

 二、懒汉模式实现

1.基本实现

2.锁+静态成员析构单例

3.双层检查锁定优化

4.双层检查锁定+智能指针

三、饿汉模式实现

1.基础实现

2.嵌套内部类解决内存泄漏

3.智能指针解决内存泄漏 


一、单例模式

单例模式(Singleton Pattern)是 一种属于创建型设计模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。(即它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。)

单例模式的三个要点

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

针对上述三要点的解决方案

1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。

2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。

3)用公有的静态函数来获取该实例:提供了访问接口。

常用的两类单例模式

1)懒汉模式:在使用类对象(单例实例)时才会去创建它,不然就不创建。

2)饿汉模式:单例实例在类装载时构建,有可能全局都没使用过,但它占用了空间,就像等着发救济粮的饿汉提前排好队等吃的一样。


 二、懒汉模式实现

1.基本实现

//singleton.h
#pragma once
#include <iostream>
using namespace std;class Singleton
{
public://公共接口获取唯一实例static Singleton* getInstance(){if (m_instance == nullptr){cout << "创建实例" << endl;m_instance = new Singleton;}return m_instance;}
private://构造私有Singleton(){cout << "调用构造函数" << endl;}  //Singleton()=default;~Singleton(){cout << "调用析构函数" << endl;} //~Singleton() = default;//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private://静态私有对象static Singleton* m_instance;
};Singleton* Singleton::m_instance = nullptr; //初始化
#include "singleton.h"int main()
{Singleton* instance1 = Singleton::getInstance();Singleton* instance2 = Singleton::getInstance();return 0;}

 执行结果:

由上述结果可知,的确只创建了一个实例。

但同时暴露了两个问题:①线程安全;②内存泄漏

①线程安全:在多线程场景下,可能多个线程进行new操作,需要加锁进行限制,保证只进行一次new操作。

#include "singleton.h"int main()
{thread t1([] {Singleton* s1 = Singleton::getInstance();});thread t2([] {Singleton* s2 = Singleton::getInstance();});t1.join();t2.join();return 0;}

 

 ②内存泄漏:new在堆上的资源在程序结束时,需要通过delete进行释放。上面并没有调用析构函数执行delete操作。

2.锁+静态成员析构单例

#include <iostream>
#include <mutex>
using namespace std;//锁+静态成员析构单例
class Singleton
{
public:static Singleton* getInstance(){m_mutex.lock();//上锁if (m_instance == nullptr){cout << "创建实例" << endl;m_instance = new Singleton;}m_mutex.unlock();//解锁return m_instance;}
private:Singleton(){cout << "调用构造函数" << endl;}  //Singleton()=default;~Singleton(){cout << "调用析构函数" << endl;} //~Singleton() = default;Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private:class FreeInstace{public:FreeInstace()=default;~FreeInstace(){if (Singleton::m_instance != nullptr){delete Singleton::m_instance;cout << "单例销毁" << endl;}}};
private://静态私有对象static Singleton* m_instance;static FreeInstace m_freeinstance;static mutex m_mutex;
};Singleton* Singleton::m_instance = nullptr; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;
mutex Singleton::m_mutex;

该方案的缺点在于对Singleton的每次访问都需要获取一个锁,锁导致速度慢,效率低。但实际上,我们只需要一个锁,初始化m_instance时(即确定m_instance指向时),这应该只在第一次调用实例时发生。如果在程序运行的过程中调用了n次instance,则只在第一次调用时需要锁。当你知道n - 1个锁是不必要的,为什么还要为n个锁的获取买单呢?

3.双层检查锁定优化

static Singleton* getInstance(){if (m_instance == nullptr){m_mutex.lock();//上锁if (m_instance == nullptr){cout << "创建实例" << endl;m_instance = new Singleton;}m_mutex.unlock();//解锁}return m_instance;}

双层检查锁定的关键是观察到大多数对instance的调用将看到m_instance是非空的,因此不会尝试初始化它。因此,它尝试获取锁之前测试m_instance是否为空。只有当测试成功(即m_instance尚未初始化)时,才会获得锁,然后再次执行测试以确保m_instance仍然为空(因此称为双重检查锁定)。第二个测试是必要的,因为,正如上面描述的情况在m_instance第一次被测试到获得锁的时间之间,有可能发生另一个线程初始化m_instance的情况

使用双层检查锁定将已经初始化的对象的直接返回。可以使代码性能会大大加快。但它们没有考虑到一个更基本的问题,即确保在双层检查锁定期间执行的机器指令以可接受的顺序执行。

m_instance = new Singleton;

这个语句导致三件事发生:
步骤1:分配内存来保存Singleton对象。
步骤2:在分配的内存中构造一个单例对象。
步骤3:使m_instance 指向已分配的内存。
最重要的是观察到编译器不受约束,会按照这个顺序执行这些步骤!

特别是,编译器有时允许交换步骤2和步骤3。所以可能导致访问到未初始化的对象的引用。

解决方案:可以参考如下链接C++完美单例模式 - 简书

4.双层检查锁定+智能指针

针对内存泄漏问题,除了可以方法2介绍的使用静态成员在程序结束时,销毁成员是调用析构进行delete,还可以使用智能指针,头文件引用<memory>

class Singleton
{
public:static shared_ptr<Singleton> getInstance(){if (m_instance == nullptr){m_mutex.lock();//上锁if (m_instance == nullptr){cout << "创建实例" << endl;m_instance.reset( new Singleton(), destoryInstance);}m_mutex.unlock();//解锁}return m_instance;}static void destoryInstance(Singleton* x) {cout << "自定义释放实例" << endl;delete x;}
private:Singleton(){cout << "调用构造函数" << endl;}//Singleton()=default;~Singleton(){cout << "调用析构函数" << endl;}//~Singleton() = default;Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private://静态私有对象static  shared_ptr<Singleton> m_instance;static mutex m_mutex;
};shared_ptr<Singleton> Singleton::m_instance = nullptr; //初始化
mutex Singleton::m_mutex;

应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。


三、饿汉模式实现

饿汉和懒汉的差别就在于,饿汉提前进行了创建。

1.基础实现

class Singleton
{
public://公共接口获取唯一实例static Singleton* getInstance(){return m_instance;}
private://构造私有Singleton(){cout << "调用构造函数" << endl;}  //Singleton()=default;~Singleton(){cout << "调用析构函数" << endl;} //~Singleton() = default;//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private://静态私有对象static Singleton* m_instance;
};Singleton* Singleton::m_instance = new Singleton; //初始化

所以main还没开始,实例就已经构建完毕。获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。

但是内存泄漏的问题还是要解决的。

2.嵌套内部类解决内存泄漏

class Singleton
{
public://公共接口获取唯一实例static Singleton* getInstance(){return m_instance;}
private://构造私有Singleton(){cout << "调用构造函数" << endl;}//Singleton()=default;~Singleton(){cout << "调用析构函数" << endl;}//~Singleton() = default;//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private:class FreeInstace{public:FreeInstace()=default;~FreeInstace(){if (Singleton::m_instance != nullptr){delete Singleton::m_instance;cout << "单例销毁" << endl;}}};
private://静态私有对象static Singleton* m_instance;static FreeInstace m_freeinstance;
};Singleton* Singleton::m_instance = new Singleton; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;

3.智能指针解决内存泄漏 

class Singleton
{
public://公共接口获取唯一实例static shared_ptr<Singleton> getInstance(){return m_instance;}static void destoryInstance(Singleton* x) {cout << "自定义释放实例" << endl;delete x;}
private://构造私有Singleton(){cout << "调用构造函数" << endl;}  //Singleton()=default;~Singleton(){cout << "调用析构函数" << endl;} //~Singleton() = default;//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private://静态私有对象static shared_ptr<Singleton> m_instance;
};shared_ptr<Singleton>  Singleton::m_instance ( new Singleton, destoryInstance); //初始化


http://www.ppmy.cn/news/916646.html

相关文章

p2p终结者ver207 注册码

原文链接&#xff1a; http://blog.csdn.net/vince6799/article/details/2872255 转载于:https://my.oschina.net/chen106106/blog/45880

【去广告版】P2P终结者4.2最高权限

P2P终结者是一款好用的p2p网络管理软件&#xff0c;能有效限制别人网速的软件&#xff0c;p2p终结者最高权限版能有效对P2P下载&#xff0c;带宽&#xff0c;聊天工具&#xff0c;web&#xff0c;流量&#xff0c;各主机进行管理。专门用来控制企业网络P2P下载流量的网络管理软…

p2p终结者,汇报csdn广大码农。

http://pan.baidu.com/s/11RLtB 亲&#xff0c;自己去下&#xff01;个人觉得这个东西还不错&#xff01;反正我所想要的结果达到了

局域网带宽控制解决方案 P2P终结者使用详解

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴! 局域网带宽控制解决方案 局域网内共享上网经常会出现某人使用迅雷,BT等P2P软件占用大部…

在Mac下防范“P2P终结者”限速

我们就手动来吧...... 先来个ARP欺骗的原理,,, ARP列表是电脑记录IP与MAC(此MAC非彼Mac)关联的一个列表,ARP欺骗是通过修改他人电脑上网关的对应MAC为自己的MAC来达到截流的目的..而对于P2P终结者这种SB软件,他有一个弱点,就是如果我把网关改成一个不相关的IP,绑定上正确的MAC,…

P2P 终结者 IP雷达

百度上都能下载 IP 雷达 是一款即时显示

P2P 终结者 for Linux

很长一段时间&#xff0c;都在渴望一个 for Linux 的 P2P 终结者。 但是&#xff0c;谁让我们是小众群体呢&#xff1f;所以&#xff0c;没有。 但是&#xff0c;今天我突发奇想&#xff0c;找到了&#xff01;用脚本实现了 P2P 终结者&#xff01; 问题是这样的&#xff0c;首…

局域网p2p终结者之类流氓软件抢占网速的原理

p2p终结者&#xff0c;它的攻击原理就是利用ARP攻击&#xff0c;通过伪造网关&#xff0c;把内网的其他的机器的发送到互联网上的数据包都需要经过假网关处理&#xff0c;在发送出去&#xff0c;这样就起到了限制其他人网速的效果。中了arp攻击一般表现为时而掉线&#xff0c;网…