前言
本文主要写懒汉单例以及单例的释放,网上很多教程只有单例的创建,但是并没有告诉我们单例的内存管理,这就很头疼。
正文
以下是两种懒汉单例的写法
1. 懒汉式单例(多线程不安全,但是在单线程里面是安全的)
创建
// Singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Singleton; }
QT_END_NAMESPACE
class Singleton : public QWidget
{
Q_OBJECT
public:
static Singleton* getInstance();
private:
// 私有化构造函数,防止外部创建实例
Singleton(QWidget *parent = nullptr);
// 禁止拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton();
private:
Ui::Singleton *ui;
// 创建静态指针变量
static Singleton* instance;
};
#endif // SINGLETON_H
// Singleton.cpp
#include "singleton.h"
#include "ui_singleton.h"
#include "qdebug.h"
// 静态变量需要在类外进行初始化
Singleton* Singleton::instance = nullptr;
Singleton *Singleton::getInstance()
{
if (instance == nullptr) {
// 使用构造函数
instance = new Singleton();
}
return instance;
}
Singleton::Singleton(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Singleton)
{
ui->setupUi(this);
}
Singleton::~Singleton()
{
qDebug()<<"单例安全销毁";
delete ui;
}
解释:
- 懒汉式在第一次调用时创建实例,延迟初始化。但未加锁,在多线程环境下不安全。
使用
//UseSingleton.h
#ifndef USESINGLETON_H
#define USESINGLETON_H
#include <QWidget>
namespace Ui {
class UseSingleton;
}
class UseSingleton : public QWidget
{
Q_OBJECT
public:
explicit UseSingleton(QWidget *parent = nullptr);
~UseSingleton();
private slots:
// 这里我在UseSingleton.ui中添加了一个按钮,用于创建单例
void on_pushButton_clicked();
private:
Ui::UseSingleton *ui;
};
#endif // USESINGLETON_H
//UseSingleton.cpp
#include "usesingleton.h"
#include "ui_usesingleton.h"
#include "singleton.h"
UseSingleton::UseSingleton(QWidget *parent) :
QWidget(parent),
ui(new Ui::UseSingleton)
{
ui->setupUi(this);
}
UseSingleton::~UseSingleton()
{
delete ui;
}
void UseSingleton::on_pushButton_clicked()
{
// 创建单例,但是这里是局部变量,只能在这里使用,也可以将创建一个单例类的成员对象
Singleton* instance = Singleton::getInstance();
instance->show();
}
内存管理
此处的单例类是作为局部变量来创建的,在更安全的懒汉中我将单例类作为成员变量来创建来展示内存管理。
- 1.当单例是一个窗口类时,我们可以重写
closeEvent
来管理内存,即使得窗口关闭时,销毁单例,代码如下
// 在Singleton类中添加如下代码
void Singleton::closeEvent(QCloseEvent *)
{
// 销毁对象
instance->deleteLater();
// 指针置空非常重要
instance = nullptr;
}
程序运行结果
当我通过按钮重复创建对象后,并且关闭单例窗口类时,单例能安全销毁。
- 1.1当单例是窗口类时,我们也可以通过,设置
Qt::WA_DeleteOnClose
属性来管理内存,代码如下:
// 在Singleton构造函数中添加
Singleton::Singleton(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Singleton)
{
ui->setupUi(this);
// 添加
this->setAttribute(Qt::WA_DeleteOnClose,true);
}
// 在析构函数中添加
Singleton::~Singleton()
{
qDebug()<<"单例安全销毁";
// 添加置空,置空非常重要
instance = nullptr;
delete ui;
}
程序运行结果
当我通过按钮重复创建对象后,并且关闭单例窗口类时,单例能安全销毁。
注意:当你按照我以上的方法管理内存时,你就不要更改我的单例,不要在栈上创建单例,否则delete栈上的空间程序直接崩溃不要来找我。
- 1.2自己管理内存。
这个你参考下面单例是非窗口类中的自己管理内存吧,都一样.
这里说明下,为什么每次销毁完对象要指针置空,因为我们存储对象的指针是静态的,所以初始化的时候只会初始化一次,要是你的单例是主程序还好,像上面我的类中单例类并不是主程序,使用单例类的类才是主程序,所以当我将单例对象销毁后(此时主程序并没有结束),再次创建单例对象的时候,程序就会崩溃,因为我的指针并不是空的,它就不会执行new那一部分,而是直接返回一个空的内容,所以程序会崩溃。感兴趣的朋友可以自己尝试下,或者我们私下交流下。 - 2 当单例类不是窗口类的时候,我们可以自己管理内存,具体实现是自己写一个销毁单例的函数,如下
// 新建一个没有窗口的类
// SingletonNoUi.h
#ifndef SINGLETONNOUI_H
#define SINGLETONNOUI_H
#include <QObject>
class SingleTonNoUi : public QObject
{
Q_OBJECT
public:
static SingleTonNoUi* getInstance();
// 销毁单例
static void destoryInstance();
private:
explicit SingleTonNoUi(QObject *parent = nullptr);
// 禁止拷贝构造和赋值操作
SingleTonNoUi(const SingleTonNoUi&) = delete;
SingleTonNoUi& operator=(const SingleTonNoUi&) = delete;
~SingleTonNoUi();
signals:
private:
// 创建静态指针变量
static SingletonNoUi* instance;
};
#endif // SINGLETONNOUI_H
// SingletonNoUi.cpp
#include "singletonnoui.h"
#include "qdebug.h"
// 初始化静态变量
SingleTonNoUi*SingleTonNoUi::instance = nullptr;
SingleTonNoUi *SingleTonNoUi::getInstance()
{
if (instance == nullptr) {
instance = new SingleTonNoUi();
}
return instance;
}
void SingleTonNoUi::destoryInstance()
{
if (instance) {
instance->deleteLater();
// 指针置空非常重要
instance = nullptr;
}
}
SingleTonNoUi::~SingleTonNoUi()
{
qDebug()<<"非窗口单例类安全销毁";
}
SingleTonNoUi::SingleTonNoUi(QObject *parent) : QObject(parent)
{
qDebug()<<"非窗口单例创建成功";
}
//在UseSingleton中再添加一个按钮,转到槽;在槽函数中添加
void UseSingleton::on_pushButton_2_clicked()
{
SingleTonNoUi* instance = SingleTonNoUi::getInstance();
/*
其它处理逻辑
*/
instance->destoryInstance();
}
程序运行结果
刚创建会被直接销毁
- 2.1使用智能指针来管理内存,但是这种方法需要对原先的单例做出一些改变,代码如下
// SingletonNoUi.h
#ifndef SINGLETONNOUI_H
#define SINGLETONNOUI_H
#include <QObject>
#include <QScopedPointer>
class SingleTonNoUi : public QObject
{
Q_OBJECT
public:
static SingleTonNoUi* getInstance();
// 需要将析构函数声明为public,要不然智能指针管理不了
~SingleTonNoUi();
private:
explicit SingleTonNoUi(QObject *parent = nullptr);
// 禁止拷贝构造和赋值操作
SingleTonNoUi(const SingleTonNoUi&) = delete;
SingleTonNoUi& operator=(const SingleTonNoUi&) = delete;
signals:
private:
// 创建静态指针变量
static QScopedPointer<SingleTonNoUi> instance;
};
#endif // SINGLETONNOUI_H
// SingletonNoUi.cpp
#include "singletonnoui.h"
#include "qdebug.h"
// 初始化静态成员变量,此处不能赋予nullptr
QScopedPointer<SingleTonNoUi> SingleTonNoUi::instance;
SingleTonNoUi *SingleTonNoUi::getInstance()
{
if(instance.isNull()) {
instance.reset(new SingleTonNoUi());
}
return instance.data();
}
SingleTonNoUi::~SingleTonNoUi()
{
qDebug()<<"非窗口单例类安全销毁";
}
SingleTonNoUi::SingleTonNoUi(QObject *parent) : QObject(parent)
{
qDebug()<<"非窗口单例创建成功";
}
//注意去掉UseSingleton类的槽函数的destoryInstace,即
void UseSingleton::on_pushButton_2_clicked()
{
SingleTonNoUi* instance = SingleTonNoUi::getInstance();
/*
其它处理逻辑
*/
}
程序运行结果
点击创建按钮后,输出框显示非窗口单例创建成功
;当我再次点击创建按钮时,没有任何变化(只要想想就会理解,因为此时我的单例类又没有被销毁,单例只能存在一个,第二个单例自然就不可能创建了),关闭主窗口,被正常销毁。
2. 懒汉式单例(线程安全)
// 还是和上面一样的类,只更改getInstance中的内容就行了
// Singleton.cpp中
#include <QMutex>
static Singleton* getInstance() {
// 添加锁机制确保线程安全
static QMutex mutex;
if (instance == nullptr) {
// 加锁,确保多线程环境下的安全性,使用locker()不用显示的解锁
QMutexLocker locker(&mutex);
// 双重检查,防止多次创建
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
解释:
- 线程安全的懒汉式单例通过
QMutex
加锁,确保在多线程环境中实例只被创建一次。
使用
这里是单例类作为成员变量时的内存管理,所以要在UseSingleton
中添加Singleton
和SingletonNoUi
这两个类的成员变量,如下
#ifndef USESINGLETON_H
#define USESINGLETON_H
#include <QWidget>
#include "singleton.h"
#include "singletonnoui.h"
namespace Ui {
class UseSingleton;
}
class UseSingleton : public QWidget
{
Q_OBJECT
public:
explicit UseSingleton(QWidget *parent = nullptr);
~UseSingleton();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::UseSingleton *ui;
// 添加两个类的成员变量
Singleton* instance;
SingleTonNoUi* instanceNoUi;
};
#endif // USESINGLETON_H
内存管理
当单例类是窗口类时:
其实和上面的单例对象作为局部变量一样。
当单例类是非窗口类时:
其实和上面的单例对象作为局部变量一样。
为什么不适用Qt中的父子机制来管理单例内存?
在Qt中,单例模式一般不使用父子机制来管理内存。因为单例模式的设计目的是保证在整个程序运行期间,某个类只有一个实例,并且它的生命周期通常贯穿整个应用程序。而Qt的父子(如QObject的父子关系)主要用于管理对象的生命周期,当父对象被销毁时,子对象也被自动销毁。单例对象的生命周期通常不与父对象绑定,所以父子机制不太适合管理单例的生命周期
小结
如有错误请指正。