【C++】C++ 单例模式总结(5种单例实现方法)

2023-05-16

本文对 C++ 的单例模式进行简单介绍和实现。

参考:

  1. C++ 线程安全的单例模式总结(强烈建议阅读原文,本文相当于做了总结,留作学习,并添加了一种新的单例方法 std::call_once )

目录

文章目录

  • 目录
  • 1. 什么是单例模式
    • 为什么需要单例模式
    • 单例模式分类
    • 单例类的特点
  • 2. 单例模式实现
    • 普通懒汉式单例(线程不安全)
    • 加锁的懒汉式单例(线程安全)
      • 方法1:返回普通指针
      • 方法2:返回智能指针
    • 静态局部变量的懒汉单例(C++11线程安全)
    • 饿汉式单例(本身就线程安全)
    • 使用 C++11 std::call_once 实现单例(C++11线程安全)

1. 什么是单例模式

单例模式是指在整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。

为什么需要单例模式

单例模式是为了保证程序的线程安全。

  • 什么是线程安全?

在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

  • 如何保证线程安全?
  1. 给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用。
  2. 让线程也拥有资源,不用去共享进程中的资源。如:使用threadlocal可以为每个线程维护一个私有的本地变量。

单例模式分类

单例模式可以分为 懒汉式饿汉式 ,两者之间的区别在于创建实例的时间不同。

  • 懒汉式

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

  • 饿汉式

系统一运行,就初始化创建实例,当需要时,直接调用即可。这种方式本身就线程安全,没有多线程的线程安全问题。

单例类的特点

  • 构造函数和析构函数为私有类型,目的是禁止外部构造和析构。
  • 拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性。
  • 类中有一个获取实例的静态方法,可以全局访问。

2. 单例模式实现

普通懒汉式单例(线程不安全)

这种情况是线程不安全的,不作详细介绍。

加锁的懒汉式单例(线程安全)

使用互斥锁保证线程安全。

方法1:返回普通指针

头文件:

///  加锁的懒汉式实现  //

class SingleInstance
{

public:
    // 获取单实例对象
    static SingleInstance *GetInstance();

    //释放单实例,进程退出时调用
    static void deleteInstance();
	
    // 打印实例地址
    void Print();

private:
    // 将其构造和析构成为私有的, 禁止外部构造和析构
    SingleInstance();
    ~SingleInstance();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    SingleInstance(const SingleInstance &signal);
    const SingleInstance &operator=(const SingleInstance &signal);

private:
    // 唯一单实例对象指针
    static SingleInstance *m_SingleInstance;
    static std::mutex m_Mutex;
};

源文件:

//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = nullptr;
std::mutex SingleInstance::m_Mutex;

// 注意:不能返回指针的引用,否则存在外部被修改的风险!
SingleInstance * SingleInstance::GetInstance()
{

    //  这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
    //  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
    if (m_SingleInstance == nullptr) 
    {
        std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
        if (m_SingleInstance == nullptr)
        {
            auto temp = new (std::nothrow) SingleInstance();
            m_SingleInstance = temp;
        }
    }

    return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = nullptr;
    }
}

void SingleInstance::Print()
{
	std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleInstance::SingleInstance()
{
    std::cout << "构造函数" << std::endl;
}

SingleInstance::~SingleInstance()
{
    std::cout << "析构函数" << std::endl;
}

方法2:返回智能指针

#include <iostream>
#include <memory>
#include <mutex>


class Singleton {

public:

    static std::shared_ptr<Singleton> getSingleton();

    void print() {
        std::cout << "Hello World." << std::endl;
    }

    ~Singleton() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

private:

    Singleton() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

static std::shared_ptr<Singleton> singleton = nullptr;
static std::mutex singletonMutex;

std::shared_ptr<Singleton> Singleton::getSingleton() {
    if (singleton == nullptr) {
        std::unique_lock<std::mutex> lock(singletonMutex);
        if (singleton == nullptr) {
            auto temp = std::shared_ptr<Singleton>(new Singleton());
            singleton = temp;
        }
    }
    return singleton;
}

静态局部变量的懒汉单例(C++11线程安全)

头文件:

///  内部静态变量的懒汉实现  //

class Single
{

public:
    // 获取单实例对象
    static Single GetInstance();
	
	// 打印实例地址
    void Print();

private:
    // 禁止外部构造
    Single();

    // 禁止外部析构
    ~Single();

    // 禁止外部拷贝构造
    Single(const Single &signal);

    // 禁止外部赋值操作
    const Single &operator=(const Single &signal);
};

源文件:

Single Single::GetInstance()
{
    /**
     * 局部静态特性的方式实现单实例。
     * 静态局部变量只在当前函数内有效,其他函数无法访问。
     * 静态局部变量只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起至程序结束止。
     */
    static Single signal;
    return signal;
}

void Single::Print()
{
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

Single::Single()
{
    std::cout << "构造函数" << std::endl;
}

Single::~Single()
{
    std::cout << "析构函数" << std::endl;
}

但是,这种方法也有点问题:在多线程场景下还是有可能会存在线程安全的问题,因为多线程同时调用 GetInstance() 方法有可能还是会产生竞争。

解决这个问题的一种做法是:在程序的单线程启动阶段就调用 GetInstance() 方法。

饿汉式单例(本身就线程安全)

头文件:

// 饿汉实现 /

class Singleton
{
public:
    // 获取单实例
    static Singleton* GetInstance();

    // 释放单实例,进程退出时调用
    static void deleteInstance();
    
    // 打印实例地址
    void Print();

private:
    // 将其构造和析构成为私有的, 禁止外部构造和析构
    Singleton();
    ~Singleton();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    Singleton(const Singleton &signal);
    const Singleton &operator=(const Singleton &signal);

private:
    // 唯一单实例对象指针
    static Singleton *g_pSingleton;
};

源文件:

// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton();

Singleton* Singleton::GetInstance()
{
    return g_pSingleton;
}

void Singleton::deleteInstance()
{
    if (g_pSingleton)
    {
        delete g_pSingleton;
        g_pSingleton = nullptr;
    }
}

void Singleton::Print()
{
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

Singleton::Singleton()
{
    std::cout << "构造函数" << std::endl;
}

Singleton::~Singleton()
{
    std::cout << "析构函数" << std::endl;
}

使用 C++11 std::call_once 实现单例(C++11线程安全)

#include <iostream>
#include <memory>
#include <mutex>

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

    void print() {
        std::cout << "Hello World." << std::endl;
    }

    ~Singleton() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

private:
    Singleton() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

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;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++】C++ 单例模式总结(5种单例实现方法) 的相关文章

  • 2011年养成的一个工作习惯

    作者 xff1a 朱金灿 来源 xff1a http blog csdn net clever101 有一句名言 xff0c 没有记录的公司 xff0c 迟早要垮掉的 xff0c 多么尖锐 个人也不是如此吗 xff1f 在下半年 xff0c
  • Cefsharp.WinForms-v112.3.0 带您最新版体验(小更新)

    一 准备 下载最新包及依赖包 对应 NET4 5 2 后续版本可能4 6 2 到packages中 本地升级更快 NuGet Gallery CefSharp WinForms 112 3 0 NuGet Gallery CefSharp
  • 无人机飞行控制算法、控制律设计软件与半物理仿真

    工业级多功能可编程飞行控制系统专业的图形化控制律设计软件灵活强大的工程应用开发平台DSP处理器及高精度传感器自定义高速遥测数据采集嵌入式半物理仿真系统丰富的用户设备接口适用于固定翼 旋翼机 特殊飞行器 车船艇 机器人 云台等 概 述 xff
  • 程序调试记录

    最近把师兄的程序在万兆网络上进行测试 xff0c 现在把调试中出现的问题进行记录 xff1a 1 xff09 其中一共是十六块板子 xff0c 板子的配置文件是sipixel xml xff0c 每块板子的配置信息里都有对应的IP xff0
  • 华清远见嵌入式学习day27——编译工具和环境搭建

    0 系统移植四天课程安排 1 编译工具 xff0c 环境搭建 2 bootloader 3 kernel 4 文件系统 1 嵌入式系统的应用领域 1 军事 2 医疗 3 移动设备 4 家电 5 工控 2 什么是嵌入式系统 一般的定义 xff
  • tf.Variable函数的用法

    tf Variable xff08 initializer xff0c name xff09 xff1a initializer是初始化参数 xff0c 可以有tf random normal xff0c tf constant xff0c
  • Docker入门操作+文件备份

    文件备份操作 bin sh it is a shell script which provides function of auto backup ecology logfiles regularly by 494389 date 61 9
  • Qnap Docker(Container Station)更改国内镜像源

    0x01qpkg环境 通常qnap市场中下载的qpkg应用 xff0c 其环境变量就在自己的包环境中 所以要修改系统中的配置 xff0c 通常需要修改qpkg应用中对应的配置 即 share CACHEDEV1 DATA qpkg xxx
  • docker源码编译(containerd+runc源码编译)

    目录 源码下载docker cli docker enginebugsgo get timeoutdebian超时git clone TLS containerd源码编译runc源码编译docker 43 containerd 43 run
  • ros中激光雷达的消息类型(sensor_msgs/LaserScan Message)说明

    最近在做一些视觉和激光数据融合的项目 xff0c 但是对激光数据的结构不是太了解 xff0c 因此查了很多相关的内容 xff0c 记录以下 下图是在http wiki ros org中截取的图片 xff1a Header 是一个结构体 xf
  • Linux下安装常见的configure错误列表

    Linux下安装常见的configure错误列表 标签 xff1a linux nginx configure 安装 错误 it 附一些常见的configure错误列表供参考 configure error No curses termca
  • 世界上不存在完美的人性

    白夜追凶 观后感 作者 xff1a 朱金灿 来源 xff1a http blog csdn net clever101 前几天看了克里斯多福 诺兰导演的惊悚电影 白夜追凶 剧情大致如下 xff1a 威尔 多莫 xff08 阿尔 帕西诺饰 x
  • k210-arduino深度学习视觉机械臂抓取

    一 arduino对机械臂的基础控制 1 首先实现arduino对机械臂的控制 xff08 点动和自动 xff09 xff0c 六个轴分别定义为xyzjkl 点动 xff1a 点动方式为按一下对应按键使对应轴正转或反转3度 xff0c 此方
  • stm32串口助手配置步骤

    串口设置的一般步骤可以总结为如下几个步骤 xff1a 1 串口时钟使能 xff0c GPIO 时钟使能 2 设置引脚复用器映射 xff1a 调用 GPIO PinAFConfig 函数 3 GPIO 初始化设置 xff1a 要设置模式为复用
  • linux查看新增串口、USB设备,Minicom + Usb转串口

    查看新增串口 USB设备 注意 xff1a 虚拟机环境下的ubuntu默认情况下是不能自动识别的 xff0c 需要在虚拟机窗口右下角点击 34 Prolific USB Serial Controller 34 xff0c 然后选择 34
  • yolo图像检测数据集格式转换:xml 与 txt格式相互转换

    格式介绍 一图流介绍的比较详细 xff0c 一般图像检测数据集格式为txt或者xml格式 xff0c 在使用labelimg进行标注的时候 xff0c 可以设置获得不同格式的数据集 xff0c 以满足不同算法训练格式要求 xff1a 一般建
  • 二分类确定画出roc曲线,以及基于roc曲线获得最佳划分阈值

    问题 在做二分类问题时候 xff0c 有正样本和负样本 构建的算法 xff0c 针对每个样本会输出一个分数值 假设该分数大小为 0 1 区间内的值 有时候单纯地以分数0 5位阈值划分样本为预测为1或者预测为0 xff0c 效果有时候并不好
  • Andrew Ng和OpenAI教你写prompt

    课程地址 xff1a https learn deeplearning ai chatgpt prompt engb站搬运 xff1a https www bilibili com video BV1No4y1t7Zn 教学人员 xff1a
  • Datawhale-chatGPT用于句词分类

    NLU基础 句子级别的分类 Token级别的分类 相关API chatGPT Style prompt建议 NLU应用 文档问答 分类 实体微调 智能对话
  • Datawhale-AIGC的人才培养

    清晰有效的提示 情景学习 思维链 提示的应用 致谢

随机推荐