Qt 之单例模式

单例模式场景

只需要一个对象的场景,比如系统日志,设备,读写配置。 比如GUI log, 一个班只有一个班主任,

  • 只能创建一个对象,并提供访问它的唯一全局访问点, 避免频繁的创建与销毁
  • 自己创建自己唯一的实例

创建方式

singleton.h

#ifndef SINGLETON
#define SINGLETON
class Singleton 
{

public:
	QString getInstanceName() const;
	static Singleton& getInstance(); //获取singleton的唯一对象
	static void release(); 
	
private:
	Singleton()    //私有的构造函数
	~Singleton();  //析构函数
	Singleton(const Singleton&other); //拷贝构造函数
	Singleton& operator=(const Singleton&other); //赋值运算操作符
	
	static QMutex mutex;
	static Singleton* instance;   //singleton 全局唯一的变量
	
}

#endif

singleton.cpp

#include "signleton.h"

QMutex Singleton::mutex;
Singleton* Singleton::instance = NULL;
Singleton::Singleton()
{
	qDebug()<<"Singleton()";
}

Singleton::~Singleton()
{
	qDebug()<<"~Singleton()"
}
Singleton& Singleton::getInstance()
{
	/*
	* 双重锁定,减少每次开销,只有越过if(NULL == instance) 才需要锁定,极低概率
	*/
	if(NULL == instance)
	{
		mutex.lock();						//线程安全
		if(NULL == instance)
		{
			instance = new Singleton();
		}
		mutex.unlock();
	}
	return *instance;
}

void Singleton::release()
{
	if(instance != NULL)
	{
		mutex.lock();
		delete instance;
		instance = NULL;
		mutex.unlock();
	}
}
QString Singleton::getInstanceName() const
{
	return "this is test for singleton";
}

main.c

#include "ConfigUtil.h"
#include <QDebug>

int main(int argc, char *argv[]) {
    Q_UNUSED(argc)
    Q_UNUSED(argv)

    qDebug() << Singleton::getInstance().getInstanceTest();
    qDebug() << Singleton::getInstance().getInstanceTest();

    Singleton::release(); // 程序结束时需要手动析构 Singleton 的对象
    return 0;
}

输出结果

Singleton()()
“this is test for singleton”
“this is test for singleton”
~Singleton()

优化

上述程序退出时,需要手动释放资源。如果系统中单例较多,那么手动释放变成一个非常大的工作量,而且有可能会出现遗漏的情况。

QScopedPointer

QScopedPointer 类似于 C++ 11 中的 unique_ptr。当我们的内存数据只在一处被使用,用完就可以安全的释放时就可以使用 QScopedPointer

  • uniqure_ptr Demo
#include <iostream>
#include <memory>

struct Task {
    int mId;
    Task(int id ) :mId(id) {
        std::cout << "Task::Constructor" << std::endl;
    }
    ~Task() {
        std::cout << "Task::Destructor" << std::endl;
    }
};

int main()
{
    // 通过原始指针创建 unique_ptr 实例
    std::unique_ptr<Task> taskPtr(new Task(23));

    //通过 unique_ptr 访问其成员
    int id = taskPtr->mId;
    std::cout << id << std::endl;

    return 0;
}

通常Qt从C++开发者手里接管了枯燥无味的垃圾回收工作,例如通过Qt自身的隐式共享,或者用QObject父子关系模型。
但是,每一次我们需要在堆上分配内存的时候,就有压力了 —— 我们需要在哪儿删除它,我们如何确保不会有内存泄露?

为了解决这个问题,QScopedPointer 诞生了。
当要超出作用域时,QScopedPointer 便会自动的删除它所指向的对象(的内存):
The QScopedPointer class stores a pointer to a dynamically allocated object, and deletes it upon destruction.

void foo()
{
QScopedPointer<int> i(newint(42));
//…
if (someCondition)
return; 
// 我们在堆上分配的一个整数将在这个位置,
// … 
} // 或者这个位置被自动的删除

单例+智能指针

优化后的Singleton.h

#ifndef SINGLETON
#define SINGLETON
class Singleton 
{

public:
	QString getInstanceName() const;
	static Singleton& getInstance(); //获取singleton的唯一对象
	static void release();
	
private:
	Singleton()    //私有的构造函数
	~Singleton();  //析构函数
	Singleton(const Singleton&other); //拷贝构造函数
	Singleton& operator=(const Singleton&other); //赋值运算操作符
	
	static QMutex mutex;
	static QScopedPointer<Singleton> instance; //静态的QScopedPointer的变量instance
	
}

#endif

优化后的Singleton.cpp

QMutex Singleton::mutex;
QScopedPointer<Singleton> Singleton::instance;

Singleton& Singleton::getInstance() {
    if (instance.isNull()) {
        mutex.lock();
        if (instance.isNull()) {
            instance.reset(new ConfigUtil());  //QScopedPointer的reset方法
        }
        mutex.unlock();
    }

    return *instance.data(); //返回指向对象的常量指针
}

优化后的main.cpp

#include "Singleton.h"
#include <QDebug>

int main(int argc, char *argv[]) {
    Q_UNUSED(argc)
    Q_UNUSED(argv)

    qDebug() << Singleton::getInstance().getInstanceTest();
    qDebug() << Singleton::getInstance().getInstanceTest();
	
	//Singleton::release();   //不需要再手动释放了
	
    return 0;
}

QScopedPointer 后记

QScopedPointer::reset()

Some operators are missing by design, for example the assignment operator:
一些常有的操作(操作符重载),在设计上是有意没有实现,例如 赋值运算符( = );

QScopedPointer<int> i(newint(42));
i = newint(43);			// 错误
i.reset(new int(43)); 	// 正确

我们设想:“reset” 看起来足够吓人了,以致于能够使读者认识到,这样会删除旧的对象,并使QScopedPointer指向新的对象.

void QScopedPointer::reset(T *other = Q_NULLPTR):delete目前指向的对象,调用其析构函数,将指针指向另一个对象other,所有权转移到other

QScopedPointer::take()和QScopedPointer::data()

使用QScopedPointer智能指针动态创建的对象,一旦出了作用域就会 被自动释放并置空,那么如果需要函数返回值怎么办呢?

int*foo()
{
	QScopedPointer<int> i(newint(42));
	//…
	return i; // thankfully, this does not compile.  谢天谢地,这无法通过编译
}

在函数返回的同时,我们的对象会被删除,因为QscopedPointer离开其作用域了.
我们将会返回一个悬垂指针,有可能导致程序崩溃。然后,当我们可以通过调用take()成员函数,告诉QScopedPointer它的工作完成了,从而接手它所指向的堆的控制权,我们的函数可能会像如下这样:

int*foo()
{
	QScopedPointer<int> i(newint(42));if (someError)
	return 0; // our integer is deleted here
	return i.take(); // from now on, our heap object is on its own.
}
  • 再来看一个例子
QLabel * createLabel()
{
    QScopedPointer<QLabel> pLabel(new QLabel());
//  return pLabel.data();  //invalid 通过data()返回过后就被自动删除了,从而导致mian函数中的p1变成了野指针,程序崩溃
    return  pLabel.take(); //valid
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QScopedPointer<QLabel> p1(createLabel());
    p1->setText("hello");
    p1->show();

    return a.exec();
}
  • T *QScopedPointer:: data() const返回指向对象的常量指针,QScopedPointer仍拥有对象所有权。所以通过data()返回过后就被自动删除了,从而导致mian函数中的p1变成了野指针,程序崩溃。

  • T *QScopedPointer:: take()也是返回对象指针,但QScopedPointer不再拥有对象所有权,而是转移到调用这个函数的caller,同时QScopePointer对象指针置为NULL

QScopedArrayPointer

如果是通过malloc分配的内存,或者通过 new[] 分配的数组呢?

为方便起见,我们用一个QScopedArrayPointer来指向那些需要被delete[]删除的对象。
同样为了方便,它还具有operator [] 运算符重载(取数组成员) ,所以我们可以这样写:

void foo()
{
	QScopedArrayPointer<int> i(newint[10]);
	i[2] = 42;return; // our integer array is now deleted using delete[]
}

超出作用域过后会自动调用delete[]删除指针,这里就不展开描述了。

问题:

  1. 为什么构造函数时private里面
  2. Call to deleted constructor of
 class S {
 public:
     static S& getInstance(){
         static S    instance; 
         return instance;
     }
     
 private:
     S() {}

 public:
     S(S const&)               = delete;
     void operator=(S const&)  = delete;
 };

 int main() {
     S bus =  S::getInstance();		//报错:Call to deleted constructor of "S" 
     return 0;
 }
  • 为什么删除public constructor
The core idea of singleton is that there is only ever one instance. If copying of the object were allowed, then there could be more than one instance. That is why the copy constructor of a singleton is deleted: To make the singleton non-copyable.
  • 报错原因
There is an error because you try to copy a non-copyable object. Don't try to copy singletons. I suspect that you were suppsed to have a reference to the singleton instead:

建议使用如下的形式:

S& bus = S::getInstance();
Qt 是一个跨平台的应用程序开发框架,通过使用 C++ 编程语言和 Qt 库,开发者可以很方便地进行应用程序的开发。Qt 提供了一系列的线程类和同步机制,用于实现多线程编程。在多线程编程中,为了保证共享资源的安全性,我们经常需要使用加锁的方式来进行同步操作。 单例模式是一种设计模式,在一个程序中只能存在一个类的对象实例。Qt 中的单例模式通常用于全局共享资源的管理,比如日志记录器、数据库管理器等。 在 Qt 中实现单例模式时,为了保证线程安全,我们需要加锁来控制多线程间对单例对象的访问。Qt 提供了 QMutex 类和 QMutexLocker 类用于加锁。 QMutex 是一个互斥量类,通过调用其 lock() 函数可以对资源加锁,这样其他线程就无法同时访问该资源。当线程完成对共享资源的操作后,需要调用 unlock() 函数来释放锁定。 使用 QMutex 加锁来实现单例模式的代码示例如下: ```cpp class Singleton { public: static Singleton* getInstance() { if (!m_instance) { QMutexLocker locker(&m_mutex); if (!m_instance) { m_instance = new Singleton(); } } return m_instance; } private: Singleton() {} static QMutex m_mutex; static Singleton* m_instance; }; QMutex Singleton::m_mutex; Singleton* Singleton::m_instance = nullptr; ``` 在 getInstance() 函数中,首先判断 m_instance 是否为空,如果为空,则使用 QMutexLocker 对象锁定 m_mutex,并再次检查 m_instance 是否为空。这样可以确保多个线程同时调用 getInstance() 函数时只有一个线程能够创建单例对象。 通过使用 QMutex 加锁来实现单例模式,可以保证在多线程环境下单例对象的安全性,避免了多线程访问带来的竞争问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值