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

C++ - 单例模式实现

时间:2023-10-11 17:34:45浏览次数:50  
标签:Singleton GetInstance single 模式 Single SingleInstance 线程 C++ 单例

1. 什么是单例模式

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

为什么需要单例模式

两个原因:

  1. 节省资源。一个类只有一个实例,不存在多份实例,节省资源。
  2. 方便控制。在一些操作公共资源的场景时,避免了多个对象引起的复杂操作。

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

 

线程安全

  • 什么是线程安全?

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

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

单例模式分类

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

  • 饿汉式:即类产生的时候就创建好实例对象,这是一种空间换时间的方式
  • 懒汉式:即在需要的时候,才创建对象,这是一种时间换空间的方式

单例类的特点

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

 

2. 单例模式实现

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

方法1:类中静态变量

存在内存泄漏问题,没有自动释放内存(不推荐使用

single.h

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

    // 打印实例地址
    void Print();

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

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

    // 禁止外部拷贝构造
    Single(const Single& single) = delete;

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


private:
    static Single* m_single;
};

single.cpp

#include "single.h"
#include <iostream>
using namespace std;

Single* Single::m_single = NULL;//在类外初始化静态变量

Single* Single::GetInstance()
{
    if (m_single == NULL)
    {
        m_single = new Single();
    }
    return m_single;
}

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

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

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

main.cpp

#include "single.h"

//最基础的懒汉式(线程不安全、内存不安全)
//有以下问题:
//1.线程不安全
//2.内存泄漏

int main()
{
    Single* s1 = Single::GetInstance();
    Single* s2 = Single::GetInstance();

    return 0;
}

执行结果:

可以看到,获取了两次类的实例,构造函数被调用一次,表明只生成了唯一实例,这是个最基础版本的单例实现,他有哪些问题呢?

1. 线程安全的问题:当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_instance_ptr是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_instance_ptr还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法:加锁

2. 内存泄漏:注意到类中只负责new出对象,却没有负责delete对象因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。

解决办法:当然我们自己手动调用delete来进行释放是可以的,但是维护在何处释放又成了问题。正确解决办法: 使用共享指针,后面会讲到;

 

方法2:类中静态变量

改进版:不存在内存泄漏问题,手动开辟手动释放(推荐使用

  • 缺点:函数调用之后需要手动释放对象,代码管理难度和内存泄漏风险提高。

single.h

class Single
{
public:
    // 获取单实例对象
    static Single* GetInstance();
    //手动释放内存
    static void FreeInstance();

    // 打印实例地址
    void Print();

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

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

    // 禁止外部拷贝构造
    Single(const Single& single) = delete;

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


private:
    static Single* m_single;
};

single.cpp

#include "single.h"
#include <iostream>
using namespace std;

Single* Single::m_single = NULL;//在类外初始化静态变量

Single* Single::GetInstance()
{
    if (m_single == NULL)
    {
        m_single = new Single();
    }
    return m_single;
}

void Single::FreeInstance()
{
    if (m_single)
    {
        delete m_single;
        m_single = nullptr;
    }
}

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

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

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

main.cpp

#include "single.h"

int main()
{
    Single* s1 = Single::GetInstance();
    Single* s2 = Single::GetInstance();
    Single::FreeInstance();
    
    return 0;
}

运行结果:

 

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

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

方法1:返回普通指针

single.h:

///  加锁的懒汉式实现  //
#include <mutex>

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;
};

single.cpp:

#include "single.h"
#include <iostream>
using namespace std;

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

// 注意:不能返回指针的引用,否则存在外部被修改的风险!
SingleInstance* SingleInstance::GetInstance()
{
    //  这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
    //  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
    if (m_SingleInstance == nullptr)
    {
        unique_lock<mutex> lock(m_Mutex); // 加锁
        if (m_SingleInstance == nullptr)
        {
            volatile auto temp = new (nothrow) SingleInstance();
            m_SingleInstance = temp;
        }
    }
    return m_SingleInstance;
}

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

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

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

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

main.cpp

#include "single.h"

int main()
{
    SingleInstance* single1 = SingleInstance::GetInstance();
    SingleInstance* single2 = SingleInstance::GetInstance();
    single1->Print();
    single2->Print();
    SingleInstance::deleteInstance();

    return 0;
}

运行结果:

 

方法2:返回智能指针

推荐使用

single.h:

#include <iostream>
#include <memory>
#include <mutex>
using namespace std;

class Singleton
{
public:

    static shared_ptr<Singleton> GetInstance();

    void print();

    ~Singleton();

private:
    Singleton();
};

single.cpp:

#include "single.h"

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

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

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

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

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

main.cpp

#include "single.h"

int main()
{
    shared_ptr<Singleton> single1 = Singleton::GetInstance();
    single1->print();
    shared_ptr<Singleton> single2 = Singleton::GetInstance();
    single2->print();

    return 0;
}

运行结果:

 

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

方法1:局部静态变量-栈区写法

单例的经典实现方式是「局部静态变量的懒汉单例」,(最推荐使用这种方式)。

single.h:

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

private:
    // 禁止外部构造
    Single();
    // 禁止外部析构
    ~Single();
    // 禁止外部拷贝构造
    Single(const Single& single) = delete;
    // 禁止外部赋值操作
    const Single& operator=(const Single& single) = delete;
};

single.cpp:

#include "single.h"
#include <iostream>
using namespace std;

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

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

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

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

main.cpp

#include "single.h"

int main()
{
    Single& s1 = Single::GetInstance();
    Single& s2 = Single::GetInstance();

    return 0;
}

运行结果:

通过在GetInstance函数中引入静态变量,达到了在不使用智能指针和锁的情况下,也能够保证线程安全和内存安全的效果,

  • C++11标准中的Magic Static特性保证线程安全,如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
  • 没有使用指针来new对象,没有内存泄漏的问题。

由于静态局部变量在程序执行到该对象的声明处时才被首次初始化,所以这也是一种懒汉式。(饿汉式用的是类的静态成员变量,不属于这个类,)

 

方法2:局部静态变量-堆区写法

用开内存的方式,动态申请内存一块内存空间(不太推荐使用

  • 缺点:函数调用之后需要手动释放对象,代码管理难度和内存泄漏风险提高。

single.h

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

    // 打印实例地址
    void Print();

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

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

    // 禁止外部拷贝构造
    Single(const Single& single) = delete;

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

single.cpp

#include "single.h"


Single* Single::GetInstance()
{
    static Single* single = NULL;
    if (single == NULL)
    {
        single = new Single();
        cout << "single = " << single << endl;
    }
    return single;
}

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

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

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

main.cpp

#include "single.h"

int main()
{
    Single* s1 = Single::GetInstance();
    cout << "s1 = " << s1 << endl;
    s1->Print();

    return 0;
}

运行结果:

注意:这里就有问题了,没有进入析构函数,没有自动释放内存,会导致内存泄漏!

既然没有自动释放,那就手动释放一下,改成如下代码:

可以看到报错了,提示说Single的析构函数不可访问,这时候需要把Single的析构函数改成公有的。

 

改完后的执行结果,如下图:

 

2.4 饿汉式单例(线程安全)

single.h:

// 饿汉实现 /

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;
};

single.cpp:

#include "single.h"
#include <iostream>
using namespace std;

// 代码一运行就初始化创建实例 ,本身就线程安全
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()
{
    cout << "我的实例内存地址是:" << this << std::endl;
}

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

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

main.cpp:

#include "single.h"

int main()
{
    Singleton* s1 = Singleton::GetInstance();
    Singleton* s2 = Singleton::GetInstance();
    Singleton::deleteInstance();

    return 0;
}

 运行结果:

 

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

#include <iostream>
#include <memory>
#include <mutex>
using namespace std;

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

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

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

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

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

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


int main()
{
    shared_ptr<Singleton> s1 = Singleton::getSingleton();
    shared_ptr<Singleton> s2 = Singleton::getSingleton();

    return 0;
}

运行结果:

 

————————————————
版权声明:本文为CSDN博主「unonoi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/unonoi/article/details/121138176

标签:Singleton,GetInstance,single,模式,Single,SingleInstance,线程,C++,单例
From: https://www.cnblogs.com/zhuchunlin/p/17757741.html

相关文章

  • C++回调C#方法
    在VC中封装的网络通信模块,在异步接收到数据时需要将内容传递给C#中的消息处理函数,于是便出现了如标题所说的情况。   C++的回调函数中有一个参数,是处理接收到的字节流的回调函数指针,定义基本如下:   typedefvoid(*fpDataReceived)(char*data,intlen);......
  • 利用模式快速导入模板代码
    在ABAP编辑器中,模式不仅可以调用函数和方法,还可以用于多种ABAP操作和语言构造,包括您自己的模式定义。定义自己的模式可以定义自己的模式快速插入统一的备注,模板代码等重复使用内容。在ABAP工作台中,选择菜单路径,实用程序>更多实用程序>编辑模式>创建模式,可以创建模式。模式的内容存......
  • C++ - 多线程之线程管理函数
    1.获取线程id函数get_id()的使用该函数在命名空间std::this_thread下。作用是获取当前线程的id。#include<iostream>#include<thread>usingnamespacestd;//No.1get_id()获取线程idvoidthreadFunc(){ cout<<"get_id()子线程id:"<<this_thread::get_id(......
  • C++ - 多线程之带返回值的线程处理函数
    1.使用async函数创建线程1.1使用步骤使用async函数启动一个异步任务(创建线程,并且执行线程处理函数),返回future对象通过future对象中get()方法获取线程处理函数的返回值1.2基本数据类型作为返回值#include<iostream>#include<thread>#include<future>using......
  • C++ - 多线程之线程同步
    1.多线程的并发问题线程间为什么需要同步?直接来看一个例子:inta=0;voidfoo(){ for(inti=0;i<10000000;++i) { a+=1; }}intmain(){ clock_tstart,end; start=clock(); threadt1(foo); threadt2(foo); t1.join(); t2.join(); end=clock();......
  • C++ - VS2019配置pthread线程库
    1.说明在VS里用MS编译器不能直接调用pthread库,需要先自行下载该库:http://sourceware.org/pub/pthreads-win32/pthreads-w32-2-9-1-release.zip解压后用的到的只有Pre-built.2文件夹下的文件。 2.配置如下图分别配置三大项:包含目录-->...pthreads-w32-2-9-1-release\Pre-......
  • C++ - TCP通信
    1.前言socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等。如下图:1.1头文件与库:#include<WinSock2.h>​#pragmacomment(lib,"ws2_32.lib")1.2准备工作:创建工程后,首先右键工程,选择属性然后选择......
  • C++ - UDP通信
    1.UDP通信流程UDP就比较简单了,步骤比tcp要少一些。连接过程图:  1.1服务器1.初始化套接字库WORDwVersion;WSADATAwsaData;interr;​wVersion=MAKEWORD(1,1);2.创建套接字SOCKETsockSrv=socket(AF_INET,SOCK_DGRAM,0);3.绑定//SOCKADDR_INaddrSrv......
  • C++ - 连接mysql数据库
    1.准备工作1.1把libmysql.dll和libmysql.lib文件复制到工程目录下首先,我们要找到刚刚开始下载的MySQL数据库的安装目录,打开目录,并且将libmysql.dll文件和libmysql.lib文件复制到工程目录下~我安装MySQL的路径:E:\mysql-5.7.42-winx64\lib把libmysql.dll文件和l......
  • C++ - 操作mysql数据库
    操作数据库的案例#include<stdio.h>#include<stdlib.h>#include<mysql.h>//固定不变的MYSQLmysql;//一个数据库结构体MYSQL_RES*res;//一个结果集结构体MYSQL_ROWrow;//char**二维数据,存放一条条记录voidconnect();//连接数据库voidinsert();//插入......