Qt 之单例模式

单例模式场景
创建方式
singleton.h
singleton.cpp
main.c
优化
QScopedPointer
单例+智能指针
优化后的Singleton.h
优化后的Singleton.cpp
优化后的main.cpp
QScopedPointer 后记
QScopedPointer::reset()
QScopedPointer::take()和QScopedPointer::data()
QScopedArrayPointer
单例模式场景
只需要一个对象的场景,比如系统日志,设备,读写配置。 比如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

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
#include

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 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 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 instance; //静态的QScopedPointer的变量instance
    
}

#endif

优化后的Singleton.cpp
QMutex Singleton::mutex;
QScopedPointer 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

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 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 i(newint(42));
    //…
    return i; // thankfully, this does not compile.  谢天谢地,这无法通过编译
}

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

int*foo()
{
    QScopedPointer 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 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 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 i(newint[10]);
    i[2] = 42;
    …
    return; // our integer array is now deleted using delete[]
}

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

/************************************************************************************

最简单的写法:c++

1
2
3
4
5
static MyClass* MyClass::Instance()
{
 static MyClass inst;
 return &inst;
}

过去很长一段时间一直都这么写,简单粗暴有效。可是直接声明静态对象会使编译出的可执行文件增大,也有可能出现其余的一些问题,因此利用了Qt自带的智能指针QScopedPointer和线程锁QMutex,改为了须要时才动态初始化的模式:安全

1
2
3
4
5
6
7
8
9
10
11
12
13
static MyClass* MyClass::Instance()
{
 static QMutex mutex;
 static QScopedPointer inst;
 if (Q_UNLIKELY(!inst)) {
 mutex.lock();
 if (!inst) {
 inst.reset(new MyClass);
 }
 mutex.unlock();
 }
 return inst.data();
}

既保证了线程安全又防止了内存泄漏,效率也没下降太多,简直完美。函数

惋惜每次都要重复这么几行实在麻烦,因而写了一个模板类:spa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template 
class Singleton
{
public:
 static T* Instance()
 {
 static QMutex mutex;
 static QScopedPointer inst;
 if (Q_UNLIKELY(!inst)) {
 mutex.lock();
 if (!inst) {
 inst.reset(new T);
 }
 mutex.unlock();
 }
 return inst.data();
 }
};

使用的时候直接这样——线程

1
MyClass* inst = Singleton::Instance();

除了用模板类,还能够利用c++中强大的宏:指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define DECLARE_SINGLETON(Class) \
Q_DISABLE_COPY(Class) \
public: \
 static Class* Instance() \
 { \
 static QMutex mutex; \
 static QScopedPointer inst; \
 if (Q_UNLIKELY(!inst)) { \
 mutex.lock(); \
 if (!inst) inst.reset(new Class); \
 mutex.unlock(); \
 } \
 return inst.data(); \
 }

而后声明的时候,填加一行这个宏:code

1
2
3
4
5
class MyClass
{
 DECLARE_SINGLETON(MyClass); // 声明单例模式
 //...
}

好评好评。对象

固然,为了要保证真的是单例模式,还要把构造函数限制为private,否则之后何时忘记了这码事,在外面又new了一下就很差了。blog

另外Qt自己自带了一个宏Q_GLOBAL_STATIC,也有相似单例模式的效果,QThreadPool::globalInstance()函数的实现就是利用了这个宏。不过它的主要用处是声明全局变量,和Singleton仍是有差异的。内存

/**********************************************************

qt既可以开发GUI程序,也可用于开发非GUI程序,是一个由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。那qt使用的什么设计模式?下面来我们就来给大家讲解一下。

其实qt的设计模式有很多种,包括单例模式、观察者模式、适配器模式等,我们今天要讲的就是qt单例模式:

单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。下面我们来看下有哪几种实现方式吧。

核心代码:构造方法私有化,private。

1、懒汉式

Qt 之单例模式_第1张图片

懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。

2、饿汉式

Qt 之单例模式_第2张图片

饿汉式,从名字上也很好理解,就是“比较勤”,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,坏处是浪费内存空间。

3、双检锁

Qt 之单例模式_第3张图片

双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。

4、静态内部类

Qt 之单例模式_第4张图片

静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

5、枚举

Qt 之单例模式_第5张图片

枚举的方式是比较少见的一种实现方式,但是看上面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。

设计模式的六大原则;

1、开闭原则

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则

里氏代换原则是面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

这就是qt单例模式的五种主要写法,一般情况下,懒汉式都比较少用;饿汉式和双检锁都可以使用,可根据具体情况自主选择;最后大家如果想要了解更多json相关知识,敬请关注奇Q工具网。

你可能感兴趣的:(QT,qt,单例模式,c++)