首页 > 编程语言 >C++单例模式

C++单例模式

时间:2024-09-08 11:46:56浏览次数:7  
标签:std 模式 instance C++ 单例 Singleton3 Singleton2 ptr delete

C++单例模式

使用单例模式的理由

在开发过程中,很多时候一个类我们希望它只创建一个对象,比如:线程池、缓存、网络请求等。当这类对象有多个实例时,程序就可能会出现异常,比如:程序出现异常行为、得到的结果不一致等。
单例主要有这两个优点:

  • 提供了对唯一实例的受控访问。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

实现单例模式主要有以下几个关键点:

  1. 构造函数设置为 private ,这避免外部通过 new 创建实例;
  2. 通过一个静态方法或者枚举返回单例类对象;
  3. 考虑对象创建时的线程安全问题,确保单例类的对象有且仅有一个,尤其是在多线程环境下;
  4. 保单例类对象在反序列化时不会重新构建对象。
  5. 考虑是否支持延迟加载;

饿汉式

在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。

class Singleton1{
public:
    static std::shared_ptr<Singleton1> getInstance(){
        return _instance_ptr;
    }
    Singleton1(const Singleton1&)=delete;
    Singleton1(Singleton1&&) = delete;
    Singleton1& operator=(const Singleton1&) = delete;
    Singleton1& operator=(Singleton1&&) = delete;

private:
    Singleton1(){std::cout << "Singleton1()" << std::endl;}    // 私有构造
    ~Singleton1(){std::cout << "~Singleton1()" << std::endl;}
    static void destructInstance() {delete _instance_ptr;};
    static std::shared_ptr<Singleton1> _instance_ptr;
};

std::shared_ptr<Singleton1> Singleton1::_instance_ptr = std::shared_ptr<Singleton1>(new Singleton1(), destructInstance);

懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载。

class Singleton2{
public:
    static std::shared_ptr<Singleton2> getInstance(){
        if(!_instance_ptr)
            _instance_ptr = std::shared_ptr<Singleton2>(
                new Singleton2(), [](Singleton2* ptr){delete ptr;});
        return _instance_ptr;
    }

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

private:
    Singleton2(){std::cout << "Singleton2()" << std::endl ;}    // 私有构造
    ~Singleton2() {std::cout << "~Singleton2()" << std::endl;}
    static std::shared_ptr<Singleton2> _instance_ptr;
};

该种方法并不线程安全,多个线程同时调用getInstance并且同时进入if分支,那么就会创建多个实例。

double-check加锁(饿汉式)

class Singleton3
{
public:
    static std::shared_ptr<Singleton3> getInstance()
    {
        if(_instance == nullptr)
        {
            lock_guard<mutex> lock(_mtx);
                if(!_instance)
                    _instance = std::shared_ptr<Singleton3>(
                        new Singleton3(), [](Singleton3* ptr){delete ptr;});
        }
        return _instance;
    }

    Singleton3(const Singleton3&) = delete;
    Singleton3(Singleton3 &&) = delete;
    Singleton3& operator=(const Singleton3&) = delete;
    Singleton3& operator=(Singleton3 &&) = delete;
private:

    Singleton3(){std::cout << "Singleton3()"<< std::endl;}    // 私有构造
    ~Singleton3() {std::cout << "~Singleton3()"<< std::endl;}
    static mutex _mtx;
    static std::shared_ptr<Singleton3> _instance;
};

new操作分三个阶段:

  1. 开辟空间;
  2. 构造对象;
  3. 返回指针。
    在多cpu多线程中,由于编译器指令重排或者是cpu指令重排,可能使得按照 1 -> 3 -> 2的方式进行;返回指针后,其他线程不会通过第一个if分支直接返回对象指针,由于此时对象还未构造完成,其他线程使用该指针将导致未定义行为。

使用静态局部变量(饿汉式)

在 C++11 中,静态局部变量的初始化是线程安全的。这意味着当多个线程并发访问一个函数时,如果该函数包含一个静态局部变量,只有第一个访问该变量的线程会执行初始化操作,而其他线程会等待初始化完成后再继续执行。C++11 的这一特性解决了多线程环境下静态局部变量可能导致的竞争条件问题。

静态局部变量在程序退出或作用域结束时会被自动销毁。C++ 会为静态局部变量调用析构函数,因此不需要手动管理其生命周期。在某些情况下,静态局部变量存储在 .data 段或 .bss 段中,这些变量会在程序终止时自动调用其析构函数。

class Singleton4{
public:
    static Singleton4* getInstance(){
      static Singleton4 instance{};
      return &instance;
    }
    Singleton4(const Singleton4&) = delete;
    Singleton4(Singleton4&&) = delete;
    Singleton4& operator=(const Singleton4&) = delete;
    Singleton4& operator=(Singleton4&&) = delete;
private:
    Singleton4(){std::cout << "Singleton4()" << std::endl;}    // 私有构造
    ~Singleton4() {std::cout << "~Singleton4()" << std::endl ;}
}

使用call_once

class Singleton5
{
public:
    static Singleton5* getInstance()
    {
        std::call_once(_initFlag, [](){_instance = new Singleton5();});
        return _instance;
    }

    Singleton5(const Singleton5&) = delete;
    Singleton5(Singleton6 &&) = delete;
    Singleton5& operator=(const Singleton5&) = delete;
    Singleton5& operator=(Singleton5&&) = delete;
private:
    Singleton5(){std::cout << "Singleton5()" << std::endl;}    // 私有构造
    ~Singleton5() {std::cout << "~Singleton5()" << std::endl ;}
    static Singleton5* _instance;
    static std::once_flag _initFlag;
};

std::once_flag Singleton5::_initFlag;

标签:std,模式,instance,C++,单例,Singleton3,Singleton2,ptr,delete
From: https://www.cnblogs.com/sfbslover/p/18402717

相关文章

  • 【C++】vector的模拟实现
    文章目录一、前言二、构造函数模拟实现构造函数调用不明确1.问题描述2、解决调用不明确的方法三、基础接口1.empty和clear2.size和capacity3.[]和iterator四、resize和reservereserve中的深浅拷贝问题1、reserve中浅拷贝发生原因2、浅拷贝发生的图解3、解决方法五、尾......
  • 【C++】简述STL——string类的使用
    文章目录一、STL的简述1.STL的框架2.STL版本二、string1、string的介绍2、为什么string类要实现为模板?三、string的构造接口四、string的容量相关的接口五、string对象修改相关的接口1、insert2.earse3、assign4、replace六、string对象字符串运算相关接口1、c_str2、......
  • C++内存管理
    内存是什么?内存就是计算机的存储空间,用于存储程序的指令、数据和状态。在C语言中,内存被组织成一系列的字节,每个字节都有一个唯一的地址。程序中的变量和数据结构存储在这些字节中。根据变量的类型和作用域,内存分为几个区域,如栈(stack)、堆(heap)和全局/静态存储区。内存编址计算......
  • C++ STL-deque容器入门详解
    1.1deque容器基本概念功能:双端数组,可以对头端进行插入删除操作deque与vector区别:vector对于头部的插入删除效率低,数据量越大,效率越低deque相对而言,对头部的插入删除速度回比vector快vector访问元素时的速度会比deque快,这和两者内部实现有关deque内部工作原理:deque内部......
  • C++ STL-Map容器从入门到精通详解
    1.简介Map也是一种关联容器,它是键—值对的集合,即它的存储都是以一对键和值进行存储的,Map通常也可以理解为关联数组(associativearray),就是每一个值都有一个键与之一一对应,因此,map也是不允许重复元素出现的。同时map也具备set的相关功能,其底层也会将元素进行自动排序。功能......
  • 【C++ Primer Plus习题】12.1
    大家好,这里是国中之林!❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看←问题:解答:main.cpp#include<iostream>#include"Cow.h"usingnamespacestd;intmain(){ Cowc1; ......
  • docker 网络模式
    1docker网络模式1.1查看网络模式dockernetworkls  1.2桥接模式bridge(默认)1.2.1概述桥接模式是docker的默认网络设置,当Docker服务启动时,会在主机上创建一个名为docker0的虚拟网桥,并选择一个和宿主机不同的IP地址和子网分配给docker0网桥1.2.1安装工具1.2.1......
  • C++编程-搜索与回溯算法2
    目录每日一诗先言正文例题六题目描述算法分析标准程序例题七:选书题目描述算法分析标准程序输出例题八:跳马问题题目描述标准程序小练习题目描述输入输出样例输入 复制样例输出 复制每日一诗红豆生南国,春来发几枝。愿君多采撷,此物最相思。Redbea......
  • Zabbix02 Zabbix告警通知, 故障自愈, 主动被动模式, JAVA应用网络设备等的监控及分布
    图形Graphs#点击web端配置下的模板,选择模板对应的图形,点击右上角创建图标#输入名称TCP状态#监控项选择添加,最后点添加#点击监测下关联该模板的主机,点击图形,就能看到添加的图形#仪表盘为图形的组合#配置下模板里,点击仪表盘栏,点击创建仪表盘,构件可把之前画的图添加出......
  • 条款05: 了解c++默默编写并调用哪些函数
    1.如果没有声明任何构造函数,编译器会为你声明一个default构造函数2.惟有default构造函数被需要,才会被编译器创建出来classEmpty{public:Empty(){}//1.默认构造~Empty(){}//2.析构函数Empty(constEmpty&rhs){}//3.copy构造Empty&operator=(c......