• Qt下实现支持多线程的单例模式



    实现方式
    1、实现单例
    把类的构造函数、拷贝构造函数、赋值操作符定义为private的;
    把获取单例的接口和唯一的实例指针定义为static的,不需要实例化,直接通过类名即可访问。
    2、支持多线程
    采用双重校验法,在获取单例的函数中使用互斥锁,确保不会出现两个线程同时new出这个单例类的实例化。
    3、解决内存泄漏
    析构单例指针,单独写一个类,利用这个类的析构函数来析构单例指针。

    Chapter1 Qt下实现支持多线程的单例模式($$$)

    原文链接:https://blog.csdn.net/lusanshui/article/details/84142869

    1. 代码介绍

    实现单例模式的代码很多。
    本文的单例模式实现代码是本人一直在工程项目中使用的,现拿出和大家交流分享。
    本文实现的单例模式,支持多线程,采用双重校验检索的方式,集成析构类,杜绝内存泄漏,稳定性好。 使用C++/Qt的朋友们可以了解一下。
    不再废话,直接上代码。

    2. 代码之路

    头文件makelog.h

    #include 
    #include 
    class Makelog: public QObject
    {
      	Q_OBJECT
      public:
      	static Makelog* getInstance()
      	{
      		if (m_pInstance == NULL)
      		{
      			QMutexLocker mlocker(&m_Mutex);  //双检索,支持多线程
      			if (m_pInstance == NULL)
      			{
      				m_pInstance = new Makelog();
      			}
      		}
      		return m_pInstance;
      	}
      private:
      	Makelog(){}
      	Makelog(const Makelog&){}
      	Makelog& operator ==(const Makelog&){}
    
      	static Makelog* m_pInstance;   //类的指针
      	static QMutex m_Mutex;
      public:
      	class CGarbo  //专用来析构m_pInstance指针的类
      	{
      		public:
      		~CGarbo()
      		{
      			if (m_pInstance != NULL)
      			{
      				delete m_pInstance;
      				m_pInstance = NULL;
      			}
      		}
      	};
      	static CGarbo m_Garbo; 					
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    makelog.cpp文件

    Makelog* Makelog::m_pInstance = NULL;
    Makelog::CGarbo m_Garbo;
    QMutex Makelog::m_Mutex;
    
    • 1
    • 2
    • 3

    支持多线程,无内存泄漏的单例模式就实现了。
    下面举例说明具体的使用:
    在头文件中加入一个public函数、一个public变量。

    public:
    void readFile();
    QString m_config;
    
    • 1
    • 2
    • 3

    在源文件中加入函数具体实现,使得readFile()或m_config有实际的意义。
    那么,在一个工程内的其他类中,只需做两个步骤,就可以使用这个readFile()函数和m_config变量了。
    步骤1:包含头文件

    #include “makefile.h”
    
    • 1

    步骤2:通过单例类入口调用函数或变量

    Makelog::getInstance()->readFile(); //使用函数
    Makelog::getInstance()->m_config; //使用变量
    
    • 1
    • 2

    3. 详细分析

    3.1 什么是单例

    单例是一种软件设计模式,采用单例模式书写的类可以确保在一个工程中只有一个对象实例。再通俗点,就是一个类写好了之后,就不需要也无法再把这个类实例化了,因为写这个类的时候已经确保了有且仅有一个已经实例化的对象。
    这样不是很蠢么?花了这么多功夫写了一个类,你告诉我这个类没法用来new出对象了?那我怎么使用这个类?我写个配合静态变量的静态函数,使用起来不是更方便?
    当然不蠢,非但不蠢,而且单例模式是所有设计模式中使用最为频繁的一个设计模式。没法new出对象,因为单例模式已经帮你new了一个对象,而且让你的工程中只有这个对象了;使用这个对象只需要包含头文件,然后调用接口指针函数就可以了;静态的全局函数或变量代码实现起来方便,但是不具有类的封装性和灵活性。

    3.2 如何让类无法实例化

    首先要清楚类实例化无非就是三种方式:
    1)采用构造函数实例化;
    2)用拷贝函数实现实例化;
    3)赋值操作实现实例化。
    所以,只需要把这个类的构造函数、拷贝函数、赋值操作写成私有的,就无法调用这些函数,自然就无法实例化了。
    正如上文所示的几个函数:

    private:
    Makelog(){} //构造函数
    Makelog(const Makelog&){} //拷贝函数
    Makelog& operator ==(const Makelog&){} //赋值操作符重写
    
    • 1
    • 2
    • 3
    • 4

    当然,如果有一些初始化的操作,也可以写在私有构造函数的双括号内。

    3.3 如何调用这个唯一实例

    广泛采用的做法就是在写一个public的函数作为接口,这个函数返回单例类唯一实例的指针。
    最简单的写法如下:

    Makelog* getInstance()
    {
    if (m_pInstance == NULL)
    {
    m_pInstance = new Makelog(); //调用private构造函数,把唯一实例的指针实例化
    }
    return m_pInstance;
    }
    Makelog* m_pInstance; //唯一实例的指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个写法看着真不错,可是这么写遇到了一个小小的悖论。
    “我如何去调用执行这个getInstance()函数啊,对,我需要一个实例化对象才能去执行!那我去new个对象,等等,唯一的实例化对象是通过这个函数才能找到的啊!”
    有方法解决么,当然,类的静态方法不需要实例化对象,用

    类名::方法()
    
    • 1

    的形式就可以调用执行了,所以把getInstance()函数前加一个static就好了。
    但是静态方法只能使用静态成员变量啊,那就把唯一实例的指针m_pInstance也变成static的吧。
    Ok,这样有没有隐患啊?隐患?static类型的instance存在静态存储区,每次调用时,都指向的同一个对象。非但没有隐患,简直堪称完美!
    现在上面的代码就变成这样:

    public:
    static Makelog* getInstance()
    {
    if (m_pInstance == NULL{
    m_pInstance = new Makelog(); //用私有构造函数new出了这个类的唯一对象
    }
    return m_pInstance;
    }
    private:
    static Makelog* m_pInstance; //唯一实例指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样写完全没有问题,但是不支持多线程的调用。因为new Makelog()需要时间,所以当两个线程同时判断m_pInstance ==NULL,同时执行了m_pInstance = new Makelog()这句代码,问题就大了。

    3.4 如何支持多线程

    为了解决3.3节产生的bug,广泛采用的方式是双重校验检索的方法。
    就是利用互斥锁(用来保证锁内代码最多只有一个线程在同时执行)的方式,确保不会出现两个线程同时new出这个单例类的唯一实例的情况发生。
    具体代码如下:

    static Makelog* getInstance()
    {
    if (m_pInstance == NULL)
    {
    QMutexLocker mlocker(&m_Mutex); //加锁,锁内代码只有一个线程执行
    if (m_pInstance == NULL) //先执行的线程会进入内部new对象,后一个线程判断m_pInstance就不是NULL了
    {
    m_pInstance = new Makelog();
    }
    }
    return m_pInstance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    至此双重校验检索解决多线程问题的单例问题就解决了。当然还可以用原子锁的方法来解决,但是灵活性不强(也可能是我太外行,灵活不起来—。—),这里就不介绍了。

    3.5 如何解决内存泄漏

    解决单例类的内存析构主要就是解决static Makelog* m_pInstance这个指针的析构问题(毕竟其他的可以不用指针的嘛)。我总结觉得写一个专门用来析构的类是最方便有效和无脑的方法了,推荐给大家。
    具体就是在单例类中写一个类:

    public:
    class CGarbo //专用来析构m_pInstance指针的类
    {
    public:
    ~CGarbo() //这个类只有析构函数
    {
    if (m_pInstance != NULL)
    {
    delete m_pInstance;
    m_pInstance = NULL;
    }
    }
    };
    static CGarbo m_Garbo; //声明一个静态的对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    然后在cpp文件中声明一下 Makelog::CGarbo m_Garbo就可以了。
    这个类只有析构函数,析构函数的作用就是delete单例唯一对象的指针。
    析构类声明一个static对象,因为静态对象系统会在关闭程序时自动析构,就可以执行到析构函数内部的代码了。

    4. 结束语

    单例模式是非常常用而基础的一个设计模式,本文作者第一次写博客,有不详或错误之处还请大家指正。对于还在使用C++/Qt的初学者,请不要因为害怕而不去深究和掌握单例模式这个好用实用的工具。

    Chapter2 Qt 全局单例类

    原文链接:https://blog.csdn.net/z_ujmn/article/details/105299528

    单例模式:将构造函数私有,能够禁止类外生成对象。将拷贝构造函数和赋值操作符重载函数声明为delete,以防生成的对象被复制。同时声明一个静态函数和静态互斥锁。静态函数用来生成对象,注意,静态函数不需要通过对象去调用。互斥锁也声明为静态是因为在静态函数里是不能访问类成员变量的,因为静态函数不需要通过对象调用,如果可以访问类成员变量,它自己也不知道访问的是哪个。

    #ifndef APPEVENT_H
    #define APPEVENT_H
    
    #include 
    #include 
    #include 
    
    class AppEvent : public QObject
    {
        Q_OBJECT
    public:
     static QSharedPointer<AppEvent> getInstance();
    
    private:
    explicit AppEvent(QObject *parent = 0);
    
        AppEvent& operator =(AppEvent&) = delete;
        AppEvent(AppEvent&) = delete;
    private:
        static QSharedPointer<AppEvent> m_appEvent_ptr;
        static QMutex m_mutex;
    signals:
    
    public slots:
    };
    
    #endif //APPEVENT_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    静态函数加锁前后分别判断一次可以提高效率。互斥锁以及判断对象是否被创建都保证只会存在一个对象。

    #include "appevent.h"
    
    AppEvent::AppEvent(QObject *parent)
    :QObject(parent)
    {}
    
    QSharedPointer<AppEvent> AppEvent::m_appEvent_ptr;
    QMutex AppEvent::m_mutex;
    
    QSharedPointer<AppEvent> AppEvent::getInstance()
    {
    	if (nullptr == m_appEvent_ptr){
    		m_mutex.lock();
    		if (nullptr == m_appEvent_ptr){
    			m_appEvent_ptr = QSharedPointer<AppEvent>(new AppEvent);
    		}
    		m_mutex.unlock();
    	}
    
    	return m_appEvent_ptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    其它文件内引用该全局单例对象时,加上单例对象文件的头文件,声明时带上extern。注意,全局单例对象只可初始化一次。

    #include 
    QSharedPointer<AppEvent> g_appEvent_ptr = AppEvent::getInstace();
    
    int main(int argc, char*argv[])
    {
    	QApplication a(argc, argv);
    	a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Chapter3 Qt实用技巧:设计模式之单例模式,唯一实例类通用模板($$$)

    原文链接:https://blog.csdn.net/qq21497936/article/details/80046081

    需求

    Qt常需要一个类,全局调用,是设计模式中的单例模式。

    单例模式

    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。

    显然单例模式的要点有三个;

    一是某个类只能有一个实例;
    二是它必须自行创建这个实例;
    三是它必须自行向整个系统提供这个实例。

    Qt单例模式示例模板(此版本重大bug)
    使用DbService::instance()全局获取该对象

    头文件

    #ifndef DBSERVICE_H
    #define DBSERVICE_H
     
    #include 
    #include 
    #include 
     
    class DbService : public QObject
    {
        Q_OBJECT
    public:
        explicit DbService(QObject *parent = 0);
     
    public:
        static DbService * instance();
     
    signals:
     
    public slots:
     
    protected:
     
     
    private:
        static DbService *_pInstance;
        static QMutex _mutex;
    };
     
    #endif // DBSERVICE_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    源文件(存在bug)

    #include "DbService.h"
     
     
    DbService * DbService::_pInstance = 0;
    QMutex DbService::_mutex;
     
    DbService::DbService(QObject *parent) : QObject(parent)
    {
     
    }
     
    DbService * DbService::instance()
    {
        if(!_pInstance)
        {
            QMutexLocker lock(&_mutex);
            if(!_pInstance)
            {
                _pInstance = new DbService();
            }
        }
        return _pInstance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    bug(感谢网友大神:火龙 的帮助)

    _pInstance = new DbService();
    
    • 1
    1. 申请DbService的内存
    2. 在申请的内存上构造DbService
    3. 将_pInstance指针指向这个内存
      这个new有这么三步

    编译器可能是132这么执行的,多个线程第一次同时使用时,可能出现野指针,即编译器先指向内存(准备第三步构造)时,另一个线程获取,则出现野指针,运行出现段错误。

    源文件(修复完bug)

    #include "DbService.h"
     
     
    DbService * DbService::_pInstance = 0;
    QMutex DbService::_mutex;
     
    DbService::DbService(QObject *parent) : QObject(parent)
    {
     
    }
     
    DbService * DbService::instance()
    {
        if(!_pInstance)
        {
            QMutexLocker lock(&_mutex);
            if(!_pInstance)
            {
                DbService *pInstance = new DbService();  // 修改处
                _pInstance = pInstance;                 // 修改处
            }
        }
        return _pInstance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    Qt单例模式示例模板(修复bug后的单例模式代码2:使用原子caozuo)

    头文件

    #ifndef DBSERVICE_H
    #define DBSERVICE_H
     
    #include 
    #include 
    #include 
     
    class DbService : public QObject
    {
        Q_OBJECT
    public:
        explicit DbService(QObject *parent = 0);
     
    public:
        static DbService *getInstance();
     
    signals:
     
    public slots:
     
    protected:
     
     
    private:
        static QAtomicPointer<DbService> _instance;
        static QMutex _mutex;
    };
     
    #endif // DBSERVICE_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    源文件

    #include "DbService.h"
     
     
    QAtomicPointer<DbService> DbService::_instance = 0;
    QMutex DbService::_mutex;
     
    DbService::DbService(QObject *parent) : QObject(parent)
    {
     
    }
     
    DbService * DbService::instance()
    {
    #ifndef Q_ATOMIC_POINTER_TEST_AND_SET_IS_ALWAYS_NATIVE
        if(!QAtomicPointer::isTestAndSetNative())//运行时检测
            qDebug() << "Error: TestAndSetNative not supported!";
    #endif
        //使用双重检测。
        /*! testAndSetOrders操作保证在原子操作前和后的的内存访问
         * 不会被重新排序。
         */
        if(_instance.testAndSetOrdered(0, 0))//第一次检测
        {
            QMutexLocker locker(&mutex);//加互斥锁。
     
            _instance.testAndSetOrdered(0, new DbService);//第二次检测。
        }
        return _instance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    ChatGPT之母:AI自动化将取代人类,创意性工作或将消失
    python excel接口自动化测试框架
    mysql索引创建语句记录
    java计算机毕业设计交通非现场执法系统源码+系统+mysql数据库+lw文档+部署
    【Linux】环境变量
    RedisTemplate出现\xac\xed\x00\x05t\x00\x0f前缀解决
    Windows 10外接屏性能挖掘
    R语言快速实现图片布局(1)
    Vue 3 + TypeScript + Vite + Element-Plus + Router + Axios + Pinia项目搭建(内含完整架构)
    计算机毕业设计ssm汽车资讯网站wlri7系统+程序+源码+lw+远程部署
  • 原文地址:https://blog.csdn.net/m0_46577050/article/details/134077041