C++单例模式、工厂模式

news2024/11/6 9:35:51

一、单例模式

(一) 什么是单例模式
1. 是什么?

在系统的整个生命周期内,一个类只允许存在一个实例。

2. 为什么?

两个原因:

  • 节省资源。
  • 方便控制,在操作公共资源的场景时,避免了多个对象引起的复杂操作。
3. 线程安全

在单例模式的实现时,需要考虑线程安全问题。

什么是线程安全?

  • 多线程并行执行共享数据,线程安全的代码是可以通过同步机制来保证各个线程正常且正确地执行,不会产生数据的污染。

如何保证线程安全?

  • 加锁,保证资源每时每刻只被一个线程占用。
  • 让线程拥有资源,不用去共享。如:threadlocal可以为每一个线程维护一个私有的本地变量。
4. 单例模式分类
  • 懒汉式

系统运行中,实例并不存在,只有当需要使用时,才去创建。需要考虑线程安全。

  • 饿汉式(未雨绸缪)

系统一运行,就初始化创建实例,当需要时,直接调用。这样是线程安全的。

5. 单例类的特点
  • 构造函数和析构函数都是私有,防止外部构造和析构
  • 拷贝构造和赋值构造都是私有,防止外部拷贝和赋值
  • 一个静态的获取实例的方法,可以全局访问
(二) 单例模式实现
1. 加锁的懒汉式单例(线程安全)
#include <mutex>
#include <iostream>

// 加锁的懒汉单例模式
class SingleInstance
{
public:
    // 获取实例
    static SingleInstance *getInstance();
    // 进程退出时,销毁实例
    static void deleteInstance();
    // 打印实例地址
    void printAddr() { std::cout << "实例的内存地址:" << this << "\n"; }

private:
    // 私有的构造和析构
    SingleInstance();
    ~SingleInstance();

    // 私有的拷贝构造和赋值构造,外部无法调用
    SingleInstance(const SingleInstance &singleInstance);
    const SingleInstance &operator=(const SingleInstance &singleInstance);

    // 对象指针和互斥锁
    static SingleInstance *singleInstance_;
    static std::mutex mutex_;
};
#include "sigleInstance.h"

SingleInstance *singleInstance_ = nullptr;
std::mutex mutex_;

// 获取实例
SingleInstance *SingleInstance::getInstance()
{   
    // 此处使用双检锁(两个if),只在指针为空的时候加锁,避免开销
    if(singleInstance_ == nullptr)
    {
        std::unique_lock<std::mutex> lock(mutex_);
        if(singleInstance_ == nullptr)
        {
            volatile auto tmp = new (std::nothrow) SingleInstance();
            singleInstance_ = tmp;
        }
    }
    return singleInstance_;
}

// 进程退出时,销毁实例
void SingleInstance::deleteInstance()
{   
    std::unique_lock<std::mutex> lock(mutex_);  // 加锁
    if(singleInstance_)
    {
        delete singleInstance_;
        singleInstance_ = nullptr;
    }
}
2. 经典实现方法:“静态局部变量”+“懒汉单例”(C++11线程安全)
class Single
{
public:
    static Single &getInstance()
    {
        // 局部静态变量只在第一次使用的时候初始化
        Single single;
        retunr single;
    }

    void print();

private:
    Single();
    ~Single();

    // 禁止拷贝和赋值
    Single(const Single &single) = delete;
    Single &operator=(const Single &single) = delete;
};
3. 饿汉式单例(线程安全)
#include <iostream>
// 饿汉模式
HungrySingle *hungrySingle_ = new (std::nothrow) HungrySingle();

class HungrySingle
{
public:
    static HungrySingle *getInstance()
    {
        return hungrySingle_;
    }

    static deleteInstance()
    {
        if(hungrySingle_)
        {
            delete hungrySingle_;
            hungrySingle_ = nullptr;
        }
    }
    void print() {}

private:
    HungrySingle() {}
    ~HungrySingle() {}

    HungrySingle(const HungrySingle& hungrySingle);
    const HungrySingle &operator= (const HungrySingle &hungrySingle);

    static HungrySingle *hungrySingle_;
};
4. 使用std::call_once实现单例(C++11线程安全)
#include <iostream>
#include <memory>
#include <mutex>

class Singleton {
public:
    static std::shared_ptr<Singleton> getSingleton();

    void print() {}
    ~Singleton() {}

private:
    Singleton() {}
};

static std::shared_ptr<Singleton> singleton = nullptr;
static std::once_flag singletonFlag;

std::shared_ptr<Singleton> Singleton::getSingleton() {
    std::call_once(singletonFlag, [&] { singleton = std::shared_ptr<Singleton>(new Singleton()); });
    return singleton;
}

二、工厂模式

(一) 什么是工厂模式
1. 是什么、为什么?

工厂模式属于创建型模式,在创建对象的时候不会对客户端暴露创建逻辑,并且通过使用一个共同的接口来指向新创建的对像。将会利用到多态和继承。

2. 工厂模式分类
  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式
(二) 分析与实现
1. 简单工厂模式

哪个火爆就生产哪个(下面用鞋厂为例)。

核心组件:

  1. 工厂类:会定义一个用于创建指定的具体实例额接口。
  2. 抽象产品类:大类(如上图Shoes),用来继承。
  3. 具体产品类:工厂类创建的对象,具体的产品。

简单工厂模式的特点:

工厂类封装了创建具体产品对象的函数。

缺陷:

拓展性差,新增产品的时候,需要修改工厂。

代码实现:

// 鞋子抽象类
class Shoes
{
public:
    virtual ~Shoes() {}
    virtual void Show() = 0;
};

class NiKeShoes : public Shoes
{
public:
    void Show() {}
};

class AdidasShoes : public Shoes
{
public:
    void Show() {}
};

class LiNingShoes : public Shoes
{
public:
    void Show() {}
};
enum SHOES_TYPE
{
    NIKE,
    LINING,
    ADIDAS
};

// 总鞋厂
class ShoesFactory
{
public:
    // 根据鞋子类型创建对应的鞋子对象
    Shoes *CreateShoes(SHOES_TYPE type)
    {
        switch (type)
        {
        case NIKE:
            return new NiKeShoes();
            break;
        case LINING:
            return new LiNingShoes();
            break;
        case ADIDAS:
            return new AdidasShoes();
            break;
        default:
            return NULL;
            break;
        }
    }
};

在main函数调用时:

int main()
{
    // 构造工厂对象
    ShoesFactory shoesFactory;

    Shoes *pNikeShoes = shoesFactory.CreateShoes(NIKE);
    if (pNikeShoes != NULL)
    {
        // 耐克球鞋广告
        pNikeShoes->Show();
        // 释放资源
        delete pNikeShoes;
        pNikeShoes = NULL;
    }

    Shoes *pLiNingShoes = shoesFactory.CreateShoes(LINING);
    if (pLiNingShoes != NULL)
    {
        // 李宁球鞋广告
        pLiNingShoes->Show();
        // 释放资源
        delete pLiNingShoes;
        pLiNingShoes = NULL;
    }

    Shoes *pAdidasShoes = shoesFactory.CreateShoes(ADIDAS);
    if (pAdidasShoes != NULL)
    {
        // 阿迪达斯球鞋广告
        pAdidasShoes->Show();
        // 释放资源
        delete pAdidasShoes;
        pAdidasShoes = NULL;
    }
    return 0;
}
2. 工厂方法模式

每种产品都有独立的生产线

核心组件:

  1. 抽象工厂类:用于继承的“大类”。
  2. 具体工厂类:继承于抽象工厂,实现创建对应具体产品对象的方法。
  3. 抽象产品类:用于继承的“大类”。
  4. 具体产品类:具体工厂创建的具体产品对象。

工厂方法模式的特点:

  • 抽象的工厂类(“大类”)。

工厂方法模式的缺陷:

  • 每多一个产品,就需要增加一个具体工厂(“生产线”),相比简单工厂模式,需要定义更多类。
  • 一个工厂只能生产一种产品。

工厂方法模式的代码:

// 总鞋厂
class ShoesFactory
{
public:
    virtual Shoes *CreateShoes() = 0;
    virtual ~ShoesFactory() {}
};

// 耐克生产者/生产链
class NiKeProducer : public ShoesFactory
{
public:
    Shoes *CreateShoes() { return new NiKeShoes(); }
};

// 阿迪达斯生产者/生产链
class AdidasProducer : public ShoesFactory
{
public:
    Shoes *CreateShoes() { return new AdidasShoes(); }
};

// 李宁生产者/生产链
class LiNingProducer : public ShoesFactory
{
public:
    Shoes *CreateShoes() { return new LiNingShoes(); }
};

main函数执行:

int main()
{
    // 鞋厂开设耐克生产线
    ShoesFactory *niKeProducer = new NiKeProducer();
    // 耐克生产线产出球鞋
    Shoes *nikeShoes = niKeProducer->CreateShoes();
    // 耐克球鞋广告
    nikeShoes->Show();
    // 释放资源
    delete nikeShoes;
    delete niKeProducer;

    // 鞋厂开设阿迪达斯生产者
    ShoesFactory *adidasProducer = new AdidasProducer();
    // 阿迪达斯生产线产出球鞋
    Shoes *adidasShoes = adidasProducer->CreateShoes();
    // 阿迪达斯球鞋广告
    adidasShoes->Show();
    // 释放资源
    delete adidasShoes;
    delete adidasProducer;

    return 0;
}
3. 抽象工厂模式

产品不止一类,比如上面提到的鞋厂也要生产运动服。

核心组件:和工厂方法模式一样

抽象工厂模式的特点:

  • 一个接口,可以创建多个产品族中的产品对象。如上图,耐克工厂->耐克衣服、耐克鞋子。

抽象工厂模式的缺陷:和工厂方法模式一样,新增产品必须添加具体工厂类

抽象工厂模式代码:

// 基类 衣服
class Clothe
{
public:
    virtual void Show() = 0;
    virtual ~Clothe() {}
};

// 耐克衣服
class NiKeClothe : public Clothe
{
public:
    void Show() {}
};

// 基类 鞋子
class Shoes
{
public:
    virtual void Show() = 0;
    virtual ~Shoes() {}
};

// 耐克鞋子
class NiKeShoes : public Shoes
{
public:
    void Show() {}
};
// 总厂
class Factory
{
public:
    virtual Shoes *CreateShoes() = 0;
	virtual Clothe *CreateClothe() = 0;
    virtual ~Factory() {}
};

// 耐克生产者/生产链
class NiKeProducer : public Factory
{
public:
    Shoes *CreateShoes() { return new NiKeShoes(); }
	
	Clothe *CreateClothe() { return new NiKeClothe(); }
};

main函数执行:

int main()
{
    // 鞋厂开设耐克生产线
    Factory *niKeProducer = new NiKeProducer();

    // 耐克生产线产出球鞋
    Shoes *nikeShoes = niKeProducer->CreateShoes();
    // 耐克生产线产出衣服
    Clothe *nikeClothe = niKeProducer->CreateClothe();

    // 耐克球鞋广告
    nikeShoes->Show();
    // 耐克衣服广告
    nikeClothe->Show();

    // 释放资源
    delete nikeShoes;
    delete nikeClothe;
    delete niKeProducer;

    return 0;
}
(三) 封装性更好的工厂模式
1. 模板工厂

对于工厂方法模式,模板编程,把工厂类改为模板工厂类。

模板工厂模式代码:

产品类:

// 基类 鞋子
class Shoes
{
public:
virtual void Show() = 0;
virtual ~Shoes() {}
};

// 耐克鞋子
class NiKeShoes : public Shoes
{
public:
void Show() {}
};

// 基类 衣服
class Clothe
{
public:
virtual void Show() = 0;
virtual ~Clothe() {}
};

// 优衣库衣服
class UniqloClothe : public Clothe
{
public:
void Show() {}
};

模板的工厂类:

// 抽象模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类
template <class AbstractProduct_t>
class AbstractFactory
{
public:
    virtual AbstractProduct_t *CreateProduct() = 0;
    virtual ~AbstractFactory() {}
};

// 具体模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类,ConcreteProduct_t 产品具体类
template <class AbstractProduct_t, class ConcreteProduct_t>
class ConcreteFactory : public AbstractFactory<AbstractProduct_t>
{
public:
    AbstractProduct_t *CreateProduct()
    {
        return new ConcreteProduct_t();
    }
};
2. 产品注册模板类 + 单例工厂模板类

在模板工厂中,对工厂类进行了模板编程的封装。不过,对于产品类,还是缺少一个可以统一随时随地获取指定产品对对象的类。

对此,我们可以把产品注册的对象用map保存起来。

// 基类,产品注册模板接口类
// 模板参数 ProductType_t 表示的类是产品抽象类
template <class ProductType_t>
class IProductRegistrar
{
public:
   // 获取产品对象抽象接口
   virtual ProductType_t *CreateProduct() = 0;

protected:
   // 禁止外部构造和虚构, 子类的"内部"的其他函数可以调用
   IProductRegistrar() {}
   virtual ~IProductRegistrar() {}

private:
   // 禁止外部拷贝和赋值操作
   IProductRegistrar(const IProductRegistrar &);
   const IProductRegistrar &operator=(const IProductRegistrar &);
};

// 工厂模板类,用于获取和注册产品对象
// 模板参数 ProductType_t 表示的类是产品抽象类
template <class ProductType_t>
class ProductFactory
{
public:
   // 获取工厂单例,工厂的实例是唯一的
   static ProductFactory<ProductType_t> &Instance()
   {
      static ProductFactory<ProductType_t> instance;
      return instance;
   }

   // 产品注册
   void RegisterProduct(IProductRegistrar<ProductType_t> *registrar, std::string name)
   {
      m_ProductRegistry[name] = registrar;
   }

   // 根据名字name,获取对应具体的产品对象
   ProductType_t *GetProduct(std::string name)
   {
      // 从map找到已经注册过的产品,并返回产品对象
      if (m_ProductRegistry.find(name) != m_ProductRegistry.end())
      {
         return m_ProductRegistry[name]->CreateProduct();
      }

      // 未注册的产品,则报错未找到
      std::cout << "No product found for " << name << std::endl;

      return NULL;
   }

private:
   // 禁止外部构造和虚构
   ProductFactory() {}
   ~ProductFactory() {}

   // 禁止外部拷贝和赋值操作
   ProductFactory(const ProductFactory &);
   const ProductFactory &operator=(const ProductFactory &);

   // 保存注册过的产品,key:产品名字 , value:产品类型
   std::map<std::string, IProductRegistrar<ProductType_t> *> m_ProductRegistry;
};

// 产品注册模板类,用于创建具体产品和从工厂里注册产品
// 模板参数 ProductType_t 表示的类是产品抽象类(基类),ProductImpl_t 表示的类是具体产品(产品种类的子类)
template <class ProductType_t, class ProductImpl_t>
class ProductRegistrar : public IProductRegistrar<ProductType_t>
{
public:
   // 构造函数,用于注册产品到工厂,只能显示调用
   explicit ProductRegistrar(std::string name)
   {
      // 通过工厂单例把产品注册到工厂
      ProductFactory<ProductType_t>::Instance().RegisterProduct(this, name);
   }

   // 创建具体产品对象指针
   ProductType_t *CreateProduct()
   {
      return new ProductImpl_t();
   }
};

关键之处:

  1. 在产品注册模板接口类中,产品的构造和析构是protected权限,用于在模板工厂注册产品。
  2. 模板工厂中,有一个map<产品名字,产品类型>,记录注册的产品。
  3. 单独一个模板产品注册类,它的构造函数用于向map注册产品,只能显式调用。

声明

工厂模式的内容是从小林coding相关文章总结而来,UML图也是来自小林coding。C++ 深入浅出工厂模式(进阶篇)_"我是耐克球鞋,我的广告语:just do it\" 工厂模式"-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_34827674/article/details/100864623

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

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

相关文章

【Python】成功解决ModuleNotFoundError: No module named ‘seaborn’

【Python】成功解决ModuleNotFoundError: No module named ‘seaborn’ &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; …

win11家庭版docker和milvus

docker 1、官网下载docker文件Get Started | Docker&#xff0c;选择download for windows下载。 2、双击打开下载好的文件Docker Desktop Installer.exe&#xff0c;add shortcut to desktop选择√代表同意添加快捷键到桌面&#xff0c;如果不勾选就说明不创建快捷键&#x…

软件报错提示缺少D3DCompiler_47.dll文件怎么解决

许多用户在运行游戏或电脑软件时&#xff0c;遇到了一个提示“找不到d3dcompiler_47.dll”的错误消息。这个问题相当普遍&#xff0c;这个错误通常是由于系统中缺少关键的d3dcompiler_47.dll文件所导致的&#xff0c;而这个文件是很多应用程序运行的必要条件&#xff0c;特别是…

浮点数的前世今生

文章目录 浮点数问题浮点数赋值和打印不同0.1累加100次&#xff0c;得到的不是10 计算机如何存储整数计算机如何存储浮点数二进制小数表示法浮点数表示小数和浮点数的转换十进制小数转换成浮点数二进制float二进制转换成十进制小数 问题解决方法参考资料 浮点数问题 浮点数赋值…

《深度学习风暴:掀起智能革命的浪潮》

在当今信息时代,深度学习已经成为科技领域的一股强大力量,其应用领域涵盖了从医疗到金融再到智能交互等方方面面。随着技术的不断进步和应用的不断拓展,深度学习的发展势头愈发迅猛,掀起了一股智能革命的浪潮。本文将从基本原理、应用实例、挑战与未来发展方向、与机器学习…

makedowm文本居中、首行缩进、回车换行

文章目录 1. 居中2. 首行缩进3. 回车换行3.1 段落中<br />换行3.2 句子中 \Enter 换行3.3 句子中 空格Enter 换行 1. 居中 由于Markdown本身不支持字体居中&#xff0c;所以采取HTML语法。如下&#xff1a; <center>这一行需要居中</center>注意: <cent…

基于PBS向超算服务器队列提交任务的脚本模板与常用命令

本文介绍在Linux服务器中&#xff0c;通过PBS&#xff08;Portable Batch System&#xff09;作业管理系统脚本的方式&#xff0c;提交任务到服务器队列&#xff0c;并执行任务的方法。 最近&#xff0c;需要在学校公用的超算中执行代码任务&#xff1b;而和多数超算设备一样&a…

仿牛客项目Day3:开发社区登录模块

发送邮件 邮箱设置 springEmail properties spring.mail.hostsmtp.qq.com spring.mail.port465 spring.mail.username spring.mail.password spring.mail.protocolsmtps spring.mail.properties.mail.smtp.ssl.enabletrue MailClient Component public class MailClient {…

计算机网络——OSI网络层次模型

计算机网络——OSI网络层次模型 应用层表示层会话层传输层TCP和UDP协议复用分用 网络层数据链路层物理层OSI网络层次模型中的硬件设备MAC地址和IP地址MAC地址IP地址MAC地址和IP地址区别 OSI网络层次模型通信过程解释端到端点到点端到端和点到点的区别 我们之前简单介绍了一下网…

数组:初始化,访问某一个,遍历

文章目录 静态初始化数组数组的访问&#xff1a;遍历数组案例 动态初始化数组总结案例 静态初始化数组 定义数组的时候直接给数组赋值。 简化格式&#xff1a; int[] ages {12,52,96}; 完整格式&#xff1a; int[] ages new int[]{12,16,26};数组变量名中存储的是数组在内存…

LINUX ADC使用

监测 ADC ,使用CAT 查看&#xff1a; LINUX ADC基本使用 &adc {pinctrl-names "default";pinctrl-0 <&adc6>;pinctrl-1 <&adc7>;pinctrl-2 <&adc8>;pinctrl-3 <&adc9>;pinctrl-4 <&adc10>;pinctrl-5 …

xxl-job学习记录

1、应用场景 例&#xff1a; 某收银系统需要在每天凌晨统计前一天的财务分析、汇总 某银行系统需要在信用卡还款日前三天发短信提醒等 2、为什么需要使用任务调度 spring中提供了注解Scheduled的注解&#xff0c;这个注解也可以实现定时任务的执行 我们只需要在方法上使用这…

安全加速SCDN在网站运营中的重要作用

SCDN&#xff08;Secure Content Delivery Network&#xff09;是一种安全加速技术&#xff0c;对于网站运营起到非常重要的作用。它能够提升用户体验&#xff0c;保护网站安全&#xff0c;提高网站的性能和可靠性。本文将详细介绍SCDN在网站运营中的作用。 首先&#xff0c;SC…

STM32基础--启动文件详解

启动文件简介&#xff08;我的建议是记住这个就行&#xff09; 启动文件由汇编编写&#xff0c;是系统上电复位后第一个执行的程序。主要做了以下工作&#xff1a; 初始化堆栈指针 SP_initial_sp &#xff08;没错就是你机组学的那个堆栈指针&#xff09;初始化 PC 指针 Rese…

《vtk9 book》 官方web版 第3章 - 计算机图形基础 (4 / 6)

3.10 将所有内容整合起来 本节概述了图形对象以及如何在 VTK 中使用它们。 图形模型 我们已经讨论了许多在场景渲染中起作用的对象。现在是将它们整合到一个全面的图形和可视化对象模型中的时候了。 在可视化工具包中&#xff0c;有七个基本对象用于渲染场景。幕后有许多其他对…

【开发】JavaWeb开发中如何解析JSON格式数据

目录 前言 JSON 的数据类型 Java 解析 JSON 常用于解析 JSON 的第三方库 Jackson Gson Fastjson 使用 Fastjson Fastjson 的优点 Fastjson 的主要对象 JSON 接口 JSONObject 类 JSONArray 类 前言 1W&#xff1a;什么是JSON&#xff1f; JSON 指 JavaScrip t对象表…

c++0305习题

一、求下面表达式的值 1&#xff0e;0 2&#xff0e;-1 3&#xff0e;1 4&#xff0e;&#xff08;1&#xff09;1 &#xff08;2&#xff09;3.2 &#xff08;3&#xff09;0 &#xff08;4&#xff09;7.0 5.&#xff08;1&#xff09;0&#xff08;2&#xff09;300.005&a…

【蓝桥·算法双周赛】第七场分级赛——小白入门赛

2.霓虹【算法赛】 - 蓝桥云课 (lanqiao.cn) st数组用来存第i个位置&#xff0c;这个字母有没有编号j #include<bits/stdc.h> const int N1e610; using lllong long; std::map<std::string,std::string> mp;std::string a,aa; int st[N][10];// int stt[N][10];//对…

Tensorflow2.0+部署(tensorflow/serving)过程备忘记录Windows+Linux

Tensorflow2.0部署&#xff08;tensorflow/serving&#xff09;过程备忘记录 部署思路&#xff1a;采用Tensorflow自带的serving进模型部署&#xff0c;采用容器docker 1.首先安装docker 下载地址&#xff08;下载windows版本&#xff09;&#xff1a;https://desktop.docke…

算法设计与分析---递归算法

递归算法 排列问题&#xff1a; 设计递归算法生成n个元素R{r1,r2,r3…rn}的全排列 将排列R个元素拆解为RiR-{ri}个元素的全排列 &#xff08;r)perm(X)表示在全排列perm(X)的每一个排列前加上前缀得到的排列 汉诺塔问题&#xff1a; void hanoi(int n,int a,int b,int c) …