Bootstrap

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();
;