QT -- 单例模式
单例模式
单利模式作为一种常用的软件设计模式,主要是用来保证系统中只有一个实例,例如一般一个程序中只有一个日志输出实例,一个系统中只有一个数据库连接实例,这时候用单例模式非常合适。
简单的单例模式
class QSingleton { public: static QSingleton* instance() { if (m_pInstance == NULL) { m_pInstance = new QSingleton(); } return m_pInstance; } static void Release() { if (m_pInstance != NULL) { delete m_pInstance; m_pInstance = NULL; } } private: QSingleton(){} QSingleton(const QSingleton&){} QSingleton& operator==(const QSingleton&){} private: static QSingleton* m_pInstance; }; // 静态成员变量需要在类体的外面进行初始化 QSingleton* QSingleton::m_pInstance = NULL;
上面的实现一种最简单的单利模式,是一种懒汉模式,所谓的懒汉模式就是在程序需要时才进行成员变量的创建也就是“延时加载”,与之相对的就是饿汉模式,饿汉模式模式就是在程序启动时就需要创建变量。懒汉模式是时间换空间,饿汉模式是空间换时间,看如下饿汉模式的一个简单实现:
class QSingleton { public: static QSingleton* instance() { return m_pInstance; } static void Release() { if (m_pInstance != NULL) { delete m_pInstance; m_pInstance = NULL; } } QSingleton(){} private: QSingleton(const QSingleton&){} QSingleton& operator==(const QSingleton&){} private: static QSingleton* m_pInstance; }; // 直接初始化静态成员变量 QSingleton* QSingleton::m_pInstance = new QSingleton;
因为程序启动时,就需要创建对象,所以单例类的默认构造函数就需要时public的,此时用户就能够创建单例类的对象,从而就不能保证单例模式的初衷:一个程序只有一个实例类,另外当我们的单例类的默认构造函数需要参数时,并且改参数需要在程序执行过程中才能够构造,此时就不能用饿汉模式的单例模式。因此下面着重对懒汉模式的单例模式实现做讨论。上面的简单的懒汉模式的单例类实现有如下缺点:
- 每次都得判断m_pInstance是否为空,增加了程序开销,而饿汉模式没有此问题。
- 需要手动调用Release函数释放静态成员变量分配内存,上面的饿汉模式也有此问题。针对此问题我们可以通过智能指针来避免。
- 不是线程安全的,要想在多线程环境下安全使用,就需要在程序一开始处,其他线程还未创建时,调用一次instance函数,但这样就抛弃了懒汉模式延迟加载的优点。饿汉模式因为在程序一开始就创建了对象,因此是线程安全的。
线程安全的单例模式
class QSingleton { public: static QSharedPointer<QSingleton>& instance() { QMutexLocker mutexLocker(&m_Mutex); if (m_pInstance.isNull()) { m_pInstance = QSharedPointer<QSingleton>(new QSingleton()); } return m_instance; } private: QSingleton(){} QSingleton(const QSingleton&){} QSingleton& operator==(const QSingleton&){} private: static QMutex m_Mutex; static QSharedPointer<QSingleton> m_pInstance; }; QMutex QSingleton::m_Mutex; QSharedPointer<QSingleton> QSingleton::m_pInstance;
通过智能指针来管理成员变量,保证了在程序退出时,自动释放内存,通过加锁保证了m_pInstance创建的唯一性,但是因为程序每次调用instance就需要先加锁,大大增加了程序开销,看如下改进实现:
class QSingleton { public: static QSharedPointer<QSingleton>& instance() { if (m_pInstance.isNull()) { QMutexLocker mutexLocker(&m_Mutex); if (m_pInstance.isNull()) m_pInstance = QSharedPointer<QSingleton>(new QSingleton()); } return m_pInstance; } private: QSingleton(){} QSingleton(const QSingleton&){} QSingleton& operator==(const QSingleton&){} private: static QMutex m_Mutex; static QSharedPointer<QSingleton> m_pInstance; }; QMutex QSingleton::m_Mutex; QSharedPointer<QSingleton> QSingleton::m_pInstance;
上面的实现通过两次检查成员变量是否为空(double-check),避免了每次调用instance函数就锁定的效率问题。
Meyers提出的一种单例模式的实现
class QSingleton { public: static QSingleton& instance() { static QSingleton qinstance; return qinstance; } private: QSingleton(){} QSingleton(const QSingleton&){} QSingleton& operator==(const QSingleton&){} };
在上述单例模式的实现中,在instance函数中声明static的局部变量,因为静态变量在程序中只会分配一次内存,保证了实例的唯一性,并且作为局部变量只有在程序第一次调用的时候才会初始化,也实现了延迟加载,而且因为不是指针变量,在程序结束时会自动回收内存,几乎就是完美的实现。虽然是只分配一次内存,但就能够确保线程安全吗?答案是否定的,因为c++的构造函数本身就不是线程安全的,当我们在构造函数内部初始化成员变量或者全局变量时,时间片就有可能被切走,我们在使用时,这一点尤为重要。
https://blog.csdn.net/Fei_Liu/article/details/69218935
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!