当前位置: 首页 > news >正文

设计模式——单例模式

        单例模式是一种创建型设计模式,它可以确保一个类在整个程序运行过程中只有一个实例,并提供一个全局访问点以获取该实例。

        单例模式的核心思想就是:控制对象的实例化,防止创建多个实例,从而节省资源并保证行为一致性。

关键点:

  • 单例类:包含单例实例的类,通常将构造函数声明为私有;
  • 静态成员变量:用于存储单例实例的静态成员变量;
  • 获取实例方法:静态方法,用于获取单例实例;
  • 私有构造函数:防止外部直接实例化单例类;
  • 线程安全处理:确保在多线程环境下单例实例的创建是安全的。
  • 构造函数和析构函数是私有的,不允许外部生成和释放
  • 静态成员变量和静态返回单例的成员函数
  • 禁用拷贝构造和赋值运算符

单例类主要是通过一个公共的静态方法getinstance接口,用于获取该类的实例,如果实例不存在,则在该方法内部创建实例并返回。

也就是说,单例类的构造方法不让其他人进行修改和使用;并且单例类只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,就只能进行调用,确保了全局只创建一个实例。

基本写法:

下面是一个最基础的实现:

class Singleton
{
private:Singleton() {}        // 私有构造函数~Singleton() {}       // 私有析构函数Singleton(const Singleton&) = delete;                // 禁用拷贝构造Singleton& operator = (const Singleton&) = delete;   // 禁用赋值操作符public:static Singleton* getInstance() {static Singleton instance;return &instance;}void show() {cout << "Singleton Show" << endl;
};// 静态成员初始化
Singleton* Singleton::instance = nullptr;int main() {Singleton::getInstance()->show();return 0;
}

其实他有两种方式懒汉模式和饿汉模式

懒汉模式

其核心是延迟初始化,可以理解为它很懒,所以它一直没有初始化,只有在首次调用getInstance()时才创建单例实例。

  • 优点:节省资源,单例对象未被使用,则不会创建。
  • 缺点:需要考虑线程安全的问题,多线程下可能会重复创建。

饿汉模式

其核心是提前初始化,即在程序启动时直接创建单例实例,无论是否被使用。

  • 优点:线程安全,实例在程序启动时初始化,避免多线程竞争。
  • 缺点:浪费资源,即使没有使用单例对象,也会占用内存。

实际开发中建议使用C++11的局部静态变量懒汉模式

实现过程分析

下面是针对懒汉模式的一个实现过程的分析

示例1

class Singleton1 {
public:// 要点2static Singleton1 * GetInstance() {if(_instance == nullptr) {_instance = new Singleton1();}return _instance;}
private:// 要点1Singleton1() {}~Singleton1() {std::cout << "~Singleton1()\n";}// 要点3Singleton1(const Singleton1 &) = delete;Singleton1& operator = (const Singleton1&) = delete;Singleton1(Singleton1 &&) = delete;Singleton1& operator = (Singleton1 &&) = delete;// 要点2static Singleton1 *_instance; 
};
Singleton1* Singleton1::_instance = nullptr;

错误的点:_instance = new Singleton1();堆上的资源不能得到正确的析构,资源还是可以释放,但是在释放的时候是无法调用析构函数的。

示例2

class Singleton2 {
public:static Singleton2 * GetInstance() {if(_instance == nullpte) {_instance = new Singleton2();atexit(Destructor);}return _instance;}
private:static void Destructor() {if(nullptr != _instance) {delete _instance;_instance = nullptr;}}Singleton2() {}~Singleton2() {std::cout << "~Singleton2()\n";}Singleton2(const Singleton2 &) = delete;Singleton2& operator = (const Singleton2&) = delete;Singleton2(Singleton2 &&) = delete;Singleton2& operator = (Singleton2 &&) = delete;static Singleton2 *_instance; 
};
Singleton2* Singleton2::_instance = nullptr;

相对于示例1,添加了一个atexit()方法,这个方法就是程序退出的时候,它会去调用Destructor函数,就可以在这个函数里面实现一个手动的析构

示例3

class Singleton3 {
public:static Singleton3 * GetInstance() {std::lock_guard<std::mutex> lock(_mutex);if(_instance == nullptr) {std::lock_guard<std::mutex> lock(_mutex);if(_instance == nullptr) {_instance = new Singleton3();// 1. 分配内存// 2. 调用构造函数// 3. 返回对象指针 atexit(Destructor);}}return _instance;}
private:static void Destructor() {if(nullptr != _instance) {delete _instance;_instance = nullptr;}}Singleton3() {}~Singleton3() {std::cout << "~Singleton3()\n";}Singleton3(const Singleton3 &) = delete;Singleton3& operator = (const Singleton3&) = delete;Singleton3(Singleton3 &&) = delete;Singleton3& operator = (Singleton3 &&) = delete;static Singleton3 *_instance; static std::mutex _mutex;
};
Singleton3* Singleton3::_instance = nullptr;
std::mutex Singleton3::_mutex;

要实现一个线程安全的单例模式,如果只用if外面那个锁,它是单检测的情况下,它总是返回那个_instance是线程安全的,缺点是,new Singleton3()只会有一次,其他情况都是拿到instance然后进行返回。两个if中间加锁是为了防止有两个线程同时进入而导致new了两次。但是也不对,没有考虑到多线程情况下,指令重排的问题。

示例4

class Singleton4 {
public:static Singleton4 * GetInstance() {Singleton4* tmp = _instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);if(tmp == nullptr) {std::lock_guard<std::mutex> lock(_mutex);tmp = _instance.load(std::memory_order_relaxed);if(tmp == nullptr) {tmp = new Singleton4();std::atomic_thread_fence(std::memory_order_release);_instance.store(tmp, std::memory_order_relaxed);atexit(Destructor);}}return tmp;}
private:static void Destructor() {Singleton4* tmp = _instance.load(std::memory_order_relaxed);if(nullptr != tmp) {delete tmp;}}Singleton4() {}~Singleton4() {std::cout << "~Singleton4()\n";}Singleton4(const Singleton4 &) = delete;Singleton4& operator = (const Singleton4&) = delete;Singleton4(Singleton4 &&) = delete;Singleton4& operator = (Singleton4 &&) = delete;static std::atomic<Singleton4*> _instance;static std::mutex _mutex;
};
std::atomic<Singleton4*> Singleton4::_instance;
std::mutex Singleton4::_mutex;

强制限制指令重排。

里面加入了获取屏障acquire fence和释放屏障release fence,也就后续的读/写操作不会重排到屏障前,且能读取其他线程的释放操作结果;释放屏障前面的读/写操作不会重排到屏障后,且保证当前线程的写入对其他线程可见。

示例5

class Singleton5 {
public:static Singleton5* GetInstance() {static Singleton5 instance;return &instance;}
private:Singleton5() {}~Singleton5() {std::cout << "~Singleton5()\n";}Singleton5(const Singleton5 &) = delete;Singleton5& operator = (const Singleton5&) = delete;Singleton5(Singleton5 &&) = delete;Singleton5& operator = (Singleton5 &&) = delete;
};

这个版本是最简单的,如果只是让你简单实现一个单例模式,可以直接写这个版本。

示例6

template<typename T>
class Singleton {
public:static T* GetInstance() {static T instance;return &instance;}
protected:Singleton() {}virtual ~Singleton() {std::cout << "~Singleton()\n";}
private:Singleton(const Singleton &) = delete;Singleton& operator = (const Singleton&) = delete;Singleton(Singleton &&) = delete;Singleton& operator = (Singleton &&) = delete;
};class DesignPattern : public Singleton<DesignPattern> {friend class Singleton<DesignPattern>;
private:DesignPattern() {}~DesignPattern() {std::cout << "~DesignPattern()\n";}
};

使用类模板把三个要点进行封装

总结:

版本1:堆上资源不能正确析构(没有调用析构函数)

版本2:堆上资源能正确析构(调用了析构函数)

版本3:双检查锁,可能造成内存泄露

版本4:线程安全,原子操作+互斥锁+内存屏障

版本5:C++11静态局部变量具备线程安全特性延迟加载内存正确释放

版本6:模板类封装了单例要点

http://www.dtcms.com/a/409850.html

相关文章:

  • C++设计模式之结构型模式:外观模式(Facade)
  • PaddleX服务化部署精度低于命令行调用的原因及解决方案
  • 新型域名前置攻击利用Google Meet、YouTube、Chrome及GCP构建流量隧道
  • 使用 C# 设置 Excel 单元格数据验证
  • python 做 网站移动互联网终端设备的主要技术指标是什么
  • Claude Code 的“AI优先”
  • 海外网站推广的公司app开发者需要更新
  • Unity-状态机复用
  • 沈阳铁西做网站公司成都移动网站建设
  • AI提示词应用
  • 【汽车篇】AI深度学习在汽车零部件外观检测——机电轴承的应用
  • 智能网联汽车技术仿真教学软件-沉浸式学习,实战化训练
  • 深圳市网站备案百度seo哪家公司好
  • 商城网站主机在线制作论坛网站
  • 网站建设扌金手指六六wordpress开源可视化编辑
  • SpringData
  • linux docker 离线 安装
  • MyBatis常见面试题
  • Docker(一)—— Docker入门到精通:从基础概念到容器管理
  • python(44) : docker compose基于基础镜像部署python服务
  • VMware+RockyLinux+ikuai+docker+cri-docker+k8s+calico BGP网络 自用 实践笔记(四)
  • 深入理解 Docker:从入门到实践
  • 实战排查:Java 解析 Excel 大型 导致内存溢出问题的完整解决过程
  • 【实录】使用 Verdaccio 从零搭建私有 npm 仓库(含完整步骤及避坑指南)
  • 物联网人体红外检测系统详解
  • 关于Unix Domain Socket的使用入门
  • 机器视觉系统中工业相机的常见类型及其特点、应用
  • RTT操作系统(4)
  • 基于卷积神经网络的 CIFAR-10 图像分类实验报告
  • 微服务项目->在线oj系统(Java-Spring)----[前端]