首页 > 其他分享 >单例模式笔记

单例模式笔记

时间:2024-01-27 16:11:07浏览次数:23  
标签:LazySingleton 模式 InnerDeletorSingleton instance static 笔记 单例 DCLSingleton

Singleton

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

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

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

注意:

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

优点

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
  • 避免对资源的多重占用(比如写文件操作)。

缺点

没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景

1、要求生产唯一序列号。

2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。

3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

4、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。

1. Lazy Singleton

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁,所以严格意义上它并不算单例模式,适用于不要求线程安全的场景。

#ifndef LAZY_SINGLETON_H
#define LAZY_SINGLETON_H

class LazySingleton
{
private:
    static LazySingleton *instance;

private:
    LazySingleton();
    ~LazySingleton();
    LazySingleton(const LazySingleton &);
    LazySingleton &operator=(const LazySingleton &);

public:
    static LazySingleton *getInstance();
};

#endif
#include "LazySingleton.h"
#include <iostream>
LazySingleton *LazySingleton::instance = nullptr;
LazySingleton *LazySingleton::getInstance()
{
    if (instance == nullptr)
    {
        instance = new LazySingleton();
    }
    return instance;
}
LazySingleton::LazySingleton()
{
    std::cout << "LazySingleton initialized!" << std::endl;
}
LazySingleton::~LazySingleton()
{
    std::cout << "LazySingleton destroyed!" << std::endl;
}

上述Lazy Singleton的实现存在内存泄露的问题,有两种解决方法:

  • 使用智能指针
  • 使用静态的嵌套类对象

1.1 使用智能指针解决内存泄漏

#ifndef SHARED_SINGLETON_H
#define SHARED_SINGLETON_H
#include <memory>
class SharedSingleton
{
private:
    static std::shared_ptr<SharedSingleton> instance;

    SharedSingleton();
    ~SharedSingleton();
    SharedSingleton(const SharedSingleton &);
    SharedSingleton &operator=(const SharedSingleton &);

public:
    static std::shared_ptr<SharedSingleton> getInstance();
};

#endif
#include "SharedSingleton.h"
#include <iostream>
#include <memory>

std::shared_ptr<SharedSingleton> SharedSingleton::instance = nullptr;
std::shared_ptr<SharedSingleton> SharedSingleton::getInstance()
{
    if (instance == nullptr)
    {
        instance = std::shared_ptr<SharedSingleton>(
          new SharedSingleton, [](SharedSingleton *obj){
            delete obj; 
          });
    }
    return instance;
}
SharedSingleton::SharedSingleton()
{
    std::cout << "SharedSingleton initialized!" << std::endl;
}
SharedSingleton::~SharedSingleton()
{
    std::cout << "SharedSingleton destroyed!" << std::endl;
}

1.2 使用静态嵌套类对象解决内存泄漏

#ifndef INNER_SINGLETON_H
#define INNER_SINGLETON_H

class InnerDeletorSingleton
{
private:
    static InnerDeletorSingleton *instance;

private:
    InnerDeletorSingleton();
    ~InnerDeletorSingleton();
    InnerDeletorSingleton(const InnerDeletorSingleton &);
    InnerDeletorSingleton &operator=(const InnerDeletorSingleton &);

private:
    class Deletor
    {
    public:
        ~Deletor()
        {
            if (InnerDeletorSingleton::instance != nullptr)
                delete InnerDeletorSingleton::instance;
        }
    };
    static Deletor deletor;

public:
    static InnerDeletorSingleton *getInstance();
};
#endif
#include "InnerDeletorSingleton.h"
#include <iostream>
InnerDeletorSingleton::Deletor InnerDeletorSingleton::deletor;
InnerDeletorSingleton *InnerDeletorSingleton::instance = nullptr;
InnerDeletorSingleton *InnerDeletorSingleton::getInstance()
{
    if (instance == nullptr)
    {
        instance = new InnerDeletorSingleton();
    }
    return instance;
}

InnerDeletorSingleton::InnerDeletorSingleton()
{
    std::cout << "InnerDeletorSingleton initialized!" << std::endl;
}
InnerDeletorSingleton::~InnerDeletorSingleton()
{
    std::cout << "InnerDeletorSingleton destroyed!" << std::endl;
}

1.3 线程安全的Singleton方案

Lazy Singleton模式的竞争条件主要出现在第一次初始化的过程中,instance = new Singleton()处,即可能多个线程同时检测到instance未被初始化,于是开始执行初始化工作,为了避免重复初始化,需要对这一过程上锁。

#ifndef DCL_SINGLETON_H
#define DCL_SINGLETON_H
#include <thread>
class DCLSingleton
{
private:
    static DCLSingleton *instance;
    DCLSingleton();
    ~DCLSingleton();
    DCLSingleton(const DCLSingleton &);
    DCLSingleton &operator=(const DCLSingleton &);
    static std::mutex mutex_;

public:
    static DCLSingleton *getInstance();
};

#endif
#include "DCLSingleton.h"
#include <iostream>
DCLSingleton *DCLSingleton::instance = nullptr;
std::mutex DCLSingleton::mutex_;
DCLSingleton *DCLSingleton::getInstance()
{
    if (instance == nullptr)
    {
        std::lock_guard<std::mutex> lk(mutex_);
        if (instance == nullptr)
        {
            instance = new DCLSingleton();
        }
    }
    return instance;
}
DCLSingleton::DCLSingleton()
{
    std::cout << "DCLSingleton initialized!" << std::endl;
}
DCLSingleton::~DCLSingleton()
{
    std::cout << "DCLSingleton destroyed!" << std::endl;
}

加入DCL后,其实还是有问题的,关于memory model。

在某些内存模型中(虽然不常见)或者是由于编译器的优化以及运行时优化等等原因,使得instance虽然已经不是nullptr但是其所指对象还没有完成构造,这种情况下,另一个线程如果调用getInstance()就有可能使用到一个不完全初始化的对象。

在C++11没有出来的时候,只能靠插入两个memory barrier(内存屏障)来解决这个错误,但是C++11引进了memory model,提供了Atomic实现内存的同步访问,即不同线程总是获取对象修改前或修改后的值,无法在对象修改期间获得该对象。

C++11后就可以正确的跨平台的实现DCL模式

std::atomic<DCLSingleton *>DCLSingleton::instance = nullptr;

2. Eager Singleton

#ifndef EAGER_SINGLETON_H
#define EAGER_SINGLETON_H
class EagerSingleton
{
private:
    static EagerSingleton instance;

    EagerSingleton();
    ~EagerSingleton();
    EagerSingleton(const EagerSingleton &);
    EagerSingleton &operator=(const EagerSingleton &);

public:
    static EagerSingleton &getInstance();
};
#endif

#include <iostream>
#include "EagerSingleton.h"
EagerSingleton EagerSingleton::instance;

EagerSingleton &EagerSingleton::getInstance()
{
    return instance;
}

EagerSingleton::EagerSingleton()
{
    std::cout << "EagerSingleton initialized!" << std::endl;
}
EagerSingleton::~EagerSingleton()
{
    std::cout << "EagerSingleton destroyed!" << std::endl;
}

由于在main函数之前初始化,所以没有线程安全的问题。

但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。

也即,static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。

3. Meyers Singleton

C++11规定了local static在多线程条件下的初始化行为,要求编译器保证了内部静态变量的线程安全性

#ifndef MEYER_SINGLETON_H
#define MEYER_SINGLETON_H
class MeyerSingleton
{
private:
	MeyerSingleton();
	~MeyerSingleton();
	MeyerSingleton(const MeyerSingleton &);
	MeyerSingleton &operator=(const MeyerSingleton &);

public:
	static MeyerSingleton &getInstance()
	{
		static MeyerSingleton instance;
		return instance;
	}
};
#endif
#include <iostream>
#include "MeyerSingleton.h"

MeyerSingleton::MeyerSingleton()
{
    std::cout << "MeyerSingleton initialized!" << std::endl;
}
MeyerSingleton::~MeyerSingleton()
{
    std::cout << "MeyerSingleton destroyed!" << std::endl;
}

4 总结

  • Eager Singleton 虽然是线程安全的,但存在潜在问题;
  • Lazy Singleton通常需要加锁来保证线程安全,但局部静态变量版本在C++11后是线程安全的;
  • 局部静态变量版本(Meyers Singleton)最优雅。

5 注意事项

现有ABC三个库,其中A中封装了一个MeyerSingleton单例类,共享库B和共享库C使用A,D作为可执行程序,使用B和C,那么这个单例是否唯一?

以下是摘自chatgpt的回答:

如果A是一个共享库,封装了一个单例类,并且B和C都使用了A,那么D作为可执行程序,使用了B和C,这个单例类在整个程序中仍然是唯一的。单例类的唯一性是相对于进程而言的,因此由A封装的单例类在整个程序执行期间只会有一个实例,即使它被不同的共享库使用。

如果A作为静态库,封装了一个单例类,B和C使用A,D作为可执行程序使用B和C。由于静态库在链接时会被整合到可执行程序中,每个使用A的库和可执行程序中都将包含单例类的一个实例。因此,这个单例类在程序中仍然是唯一的,但是这个唯一性是相对于每个包含A的模块而言的,而不是整个程序。每个模块(B、C和D)都会有自己的单例实例。

此外,当libA作为静态库且在cpp文件中实现getInstance时,libB和libC同时作为动态库时出现单例不一致的问题。当libA作为动态库时,libB和libC作为动态库或者静态库时都没有这个问题 所以为了避免这个问题,最好的方式是,将getInstance的实现内联在.h文件中

标签:LazySingleton,模式,InnerDeletorSingleton,instance,static,笔记,单例,DCLSingleton
From: https://www.cnblogs.com/pengpengda/p/17991575

相关文章

  • 静态库中单例不唯一的情况
    提出问题A作为共享库,封装了一个单例类,共享库B和共享库C使用A,D作为可执行程序,使用B和C,那么这个单例是否唯一?实验首先创建一个C++项目,项目结构如下.├──CMakeLists.txt├──MeyerSingleton.cpp├──MeyerSingleton.h├──testlib.cpp├──testlibheader1.cpp├......
  • 1/27 学习进度笔记
    今日学习了DataFrame的代码构建--读取外部数据读取数据源包括text,csv,json,parquet四种数据源schema=StructType().add("data",StringType(),nullable=True)df=spark.read.format("text").\schema(schema=schema).\load("../data/sql/people.txt")df=......
  • MyBatis注解模式和优化
    MyBatis注解模式之前我们使用xml文件方式实现sql语句的编写,我们也可以使用注解模式编写sql语句。前面的基本配置一致,不再叙述。第一步:创建实体类根据数据库的列名与表名设计实体类数据库信息:(表名t_student)实体类:@Data@NoArgsConstructor@AllArgsConstructorpubliccla......
  • HCCF论文阅读笔记
    Abstract存在的挑战:使用更深层次的基于图的CF架构有过平滑效应,会导致难以区分的用户表示和推荐结果的退化监督信号在现实中是稀疏和偏态分布的,限制了CF范式的表达能力提出了一种新的自监督框架超图对比协同过滤(HCCF),通过一个超图增强的交叉视图对比学习架构来联合捕获局部......
  • Linux基础命令笔记(黑马)
    Linux基础命令Linux常用快捷键ctrl+c:强制停止程序运行ctrl+d:退出用户登录或某些特定程序的专属页面(不能用于vim)!历史命令前缀:执行历史中最后使用带有该命令前缀的命令例:!p相当于python、!t相当于tailctrl+r:可输入历史命令关键字搜索到想要到命令,按回车直接执行,按左......
  • 备忘录模式
    用一个栈保存一个对象的一系列历史状态,在需要的时候可以恢复对象定义:保存一个对象的某个状态,以便在适当的时候恢复对象,即“后悔药”类型:行为型适用场景:保存及恢复数据相关业务场景后悔的时候,即想恢复到之前的状态优点:为用户提供一种可恢复机制存档信息的封装缺点:......
  • 关于javascript的一些笔记(一)
    在script标签内使用import的时候,必须在script标签加上type=“module”当使用script标签加上type="module"的时候,是当所有模块都加载进来才进行工作的,也就是如果html在script标签下面也是可以正常运行的,他是后解析的当使用script标签加上type="module"的时候,script标签里面......
  • 数位 dp 学习笔记(灵神模板)
      我谔谔,数位dp几年了还不会,学的都是些奇奇怪怪的写法,导致每次比赛遇到数位dp的题要么不会,要么写半天。灵神的数位dp模板其实很早就有看过,不过当时不是很理解递归的含义于是放弃了,最近重新来回来看发现还是递归好写,不仅简短而且基本都是一个框架,这就可以大大减少思考量,基......
  • 构建之法的读书笔记与读后感1
    概论软件工程中的概念,源程序,软件构建,需求分析,软件项目的管理,国际化和本地化。各种商业模式的介绍,提出了职业道德规范。“现在回头看本节开头的疑惑,答案就很清楚了,程序(算法、数据结构)是基本功,但是在算法和数据结构之上,软件工程决定了软件的质量;商业模式决定了一个软件企业的成败......
  • 2024 笔记类软件对比(非常主观)
    在线的离线的自用之后的体验主要关注易用性,持久性,价格敏感Logseq:软件在github上开源,文档都是本地的markdown文件形式。工作上用它记了差不多一年,双向链接功能很好用。card和画板功能我没怎么用。因为同步不方便/记录需要先组织好逻辑,逐渐放弃Notion:白嫖了一个教育plan,不......