单例模式场景
创建方式
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 <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[]删除指针,这里就不展开描述了。
/************************************************************************************
最简单的写法: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<MyClass> 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 T> class Singleton { public: static T* Instance() { static QMutex mutex; static QScopedPointer<T> inst; if (Q_UNLIKELY(!inst)) { mutex.lock(); if (!inst) { inst.reset(new T); } mutex.unlock(); } return inst.data(); } }; |
使用的时候直接这样——线程
1 | MyClass* inst = Singleton<MyClass>::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<Class> 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、懒汉式
懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。
2、饿汉式
饿汉式,从名字上也很好理解,就是“比较勤”,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,坏处是浪费内存空间。
3、双检锁
双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
4、静态内部类
静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
5、枚举
枚举的方式是比较少见的一种实现方式,但是看上面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。
设计模式的六大原则;
1、开闭原则
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则
里氏代换原则是面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
这就是qt单例模式的五种主要写法,一般情况下,懒汉式都比较少用;饿汉式和双检锁都可以使用,可根据具体情况自主选择;最后大家如果想要了解更多json相关知识,敬请关注奇Q工具网。