1. Qt使用标准c++单例类模板出现的问题
之前写过一篇c++单例类模板,但是那个只适用于标准c++的类。在Qt中使用时遇到了问题。
我的使用场景:有些界面参数配置或者内容需要全局访问或者要在多个地方显示,但是所有地方需要保持数据的一致性,所以使用单例保证进程全局只有一个实例。但是Qt的界面类顶层都是继承自QObject的,界面一旦被设置了parent之后,对象的控制权便交给了Qt,由Qt做内存管理。界面关闭的时候,Qt会自动按照父子顺序来销毁界面实例,也就是我的单例界面实例内存被系统回收,但是地址不为空。
如果我使用c++的类模板,程序退出时,gc也会进行内存释放,但此时Qt已经将实例释放掉了,这个地址空间已经被系统回收,无法再访问,此时就会导致重复析构,程序会报错。
正常释放顺序应该是下面这样:
init instance // 程序启动输出init
delete instance // 关闭window后,gc释放实例
delete myclass // gc中delete m_pInstance 会调用MyClass的析构函数。
但是当我使用c++的单例类模板给Qt的界面使用时,程序退出时,析构顺序是这样的:
init instance // 程序启动输出init
delete myclass ui // Qt根据对象树释放内存,会调用MyClassUI的析构函数。此时我的界面实例已经被销毁,内存被系统回收。
delete instance // 程序退出,gc释放实例,此时delete想要访问的是系统保护的内存,程序便会报错。
2. 解决方法
使用Qt的方法管理内存:QObjectCleanupHandler
QObjectCleanupHandler 可以监视多个QObject对象的生命周期。并且最大的优点是,当对象在别的地方被删除后,会自动从QObjectCleanupHandler 中移除,并且可以通过isEmpty()来判断当前QObjectCleanupHandler 中是否还有监视对象。然后可以使用clear()方法直接删除所有的监视对象,而且当QObjectCleanupHandler 对象析构后,也会自动删除所有监视对象。
3. 具体实现
#include <mutex>
#include <QObjectCleanupHandler>
template <class T>
class QSingleton
{
private:
static T* m_pInstance;
QSingleton(const QSingleton& other) {}
QSingleton& operator=(const QSingleton& other) {}
// class Gc
// {
// public:
// ~Gc()
// {
// if (!QSingleton::m_cleanupHandler.isEmpty()) {
// QSingleton::m_cleanupHandler.clear();
// }
// }
// };
static QObjectCleanupHandler m_cleanupHandler; //声明变量
protected:
QSingleton() {}
~QSingleton() {}
public:
static T* getInstance()
{
static std::mutex lock;
// static Gc gc; // m_cleanupHandler是静态全局变量,程序退出时会析构,并删除其所监视的对象,所以不需要gc
if (m_pInstance == nullptr) {
std::lock_guard<std::mutex> locker(lock);
if (m_pInstance == nullptr)
{
m_pInstance = new T();
m_cleanupHandler.add(m_pInstance);
}
}
return m_pInstance;
}
};
template <class T>
T* QSingleton<T>::m_pInstance = nullptr;
template <class T>
QObjectCleanupHandler QSingleton<T>::m_cleanupHandler; // 类外初始化变量,初始化后才可以使用,否则不起作用
使用方法和c++单例类模板相同,可以参考上一篇关于c++单例类模板的博文
4. 测试结果
输出结果:
- 不给单例界面类指定父对象:
clean up instance // 由m_cleanupHandler释放实例,触发界面的析构函数
myclass ui delete // 执行界面析构函数输出
- 给界面类指定父对象:
parent main window delete // 程序退出父界面析构输出,同时析构子对象
myclass ui delete // myclassui作为子对象被析构,执行界面析构函数输出,此过程由Qt控制。再到gc释放,
// m_cleanupHandler.isEmpty()判断为空,不会重复析构此单例的实例。
所以继承自QObject的类可以应用此版本的单例类模板,不能用标准c++的版本。