一杯清酒邀明月
天下本无事,庸人扰之而烦耳。
posts - 3121,comments - 209,views - 578万

单例模式

  单利模式作为一种常用的软件设计模式,主要是用来保证系统中只有一个实例,例如一般一个程序中只有一个日志输出实例,一个系统中只有一个数据库连接实例,这时候用单例模式非常合适。

简单的单例模式

复制代码
 1 class QSingleton
 2 {
 3 public:
 4     static QSingleton* instance()
 5     {
 6         if (m_pInstance == NULL)
 7         {
 8             m_pInstance = new QSingleton();
 9         }
10         return m_pInstance;
11     }
12 
13     static void Release()
14     {
15         if (m_pInstance != NULL)
16         {
17             delete m_pInstance;
18             m_pInstance = NULL;
19         }
20     }
21 private:
22     QSingleton(){}
23     QSingleton(const QSingleton&){}
24     QSingleton& operator==(const QSingleton&){}
25 private:
26     static QSingleton* m_pInstance;
27 };
28 // 静态成员变量需要在类体的外面进行初始化
29 QSingleton* QSingleton::m_pInstance = NULL;
复制代码

  上面的实现一种最简单的单利模式,是一种懒汉模式,所谓的懒汉模式就是在程序需要时才进行成员变量的创建也就是“延时加载”,与之相对的就是饿汉模式,恶汉模式就是在程序启动时就需要创建变量。懒汉模式是时间换空间,恶汉模式是空间换时间,看如下恶汉模式的一个简单实现:

复制代码
 1 class QSingleton
 2 {
 3 public:
 4     static QSingleton* instance()
 5     {
 6         return m_pInstance;
 7     }
 8 
 9     static void Release()
10     {
11         if (m_pInstance != NULL)
12         {
13             delete m_pInstance;
14             m_pInstance = NULL;
15         }
16     }
17     QSingleton(){}
18 
19 private:
20     QSingleton(const QSingleton&){}
21     QSingleton& operator==(const QSingleton&){}
22 private:
23     static QSingleton* m_pInstance;
24 };
25 
26 // 直接初始化静态成员变量
27 QSingleton* QSingleton::m_pInstance = new QSingleton;
复制代码

  因为程序启动时,就需要创建对象,所以单例类的默认构造函数就需要时public的,此时用户就能够创建单例类的对象,从而就不能保证单例模式的初衷:一个程序只有一个实例类,另外当我们的单例类的默认构造函数需要参数时,并且改参数需要在程序执行过程中才能够构造,此时就不能用饿汉模式的单例模式。因此下面着重对懒汉模式的单例模式实现做讨论。上面的简单的懒汉模式的单例类实现有如下缺点:

  每次都得判断m_pInstance是否为空,增加了程序开销,而饿汉模式没有此问题。
  需要手动调用Release函数释放静态成员变量分配内存,上面的饿汉模式也有此问题。针对此问题我们可以通过智能指针来避免。
  不是线程安全的,要想在多线程环境下安全使用,就需要在程序一开始处,其他线程还未创建时,调用一次instance函数,但这样就抛弃了懒汉模式延迟加载的优点。饿汉模式因为在程序一开始就创建了对象,因此是线程安全的。
线程安全的单例模式

复制代码
 1 class QSingleton
 2 {
 3 public:
 4     static QSharedPointer<QSingleton>& instance()
 5     {
 6         QMutexLocker mutexLocker(&m_Mutex);
 7         if (m_pInstance.isNull())
 8         {
 9             m_pInstance = QSharedPointer<QSingleton>(new QSingleton());
10         }
11         return m_instance;
12     }
13 private:
14     QSingleton(){}
15     QSingleton(const QSingleton&){}
16     QSingleton& operator==(const QSingleton&){}
17 private:
18     static QMutex m_Mutex;
19     static QSharedPointer<QSingleton> m_pInstance;
20 };
21 
22 QMutex QSingleton::m_Mutex;
23 QSharedPointer<QSingleton> QSingleton::m_pInstance;
复制代码

  通过智能指针来管理成员变量,保证了在程序退出时,自动释放内存,通过加锁保证了m_pInstance创建的唯一性,但是因为程序每次调用instance就需要先加锁,大大增加了程序开销,看如下改进实现:

复制代码
 1 class QSingleton
 2 {
 3 public:
 4     static QSharedPointer<QSingleton>& instance()
 5     {
 6 
 7         if (m_pInstance.isNull())
 8         {
 9             QMutexLocker mutexLocker(&m_Mutex);
10             if (m_pInstance.isNull())
11                 m_pInstance = QSharedPointer<QSingleton>(new QSingleton());
12         }
13         return m_pInstance;
14     }
15 private:
16     QSingleton(){}
17     QSingleton(const QSingleton&){}
18     QSingleton& operator==(const QSingleton&){}
19 private:
20     static QMutex m_Mutex;
21     static QSharedPointer<QSingleton> m_pInstance;
22 };
23 
24 QMutex QSingleton::m_Mutex;
25 QSharedPointer<QSingleton> QSingleton::m_pInstance;
复制代码

  上面的实现通过两次检查成员变量是否为空(double-check),避免了每次调用instance函数就锁定的效率问题。

Meyers提出的一种单例模式的实现

复制代码
 1 class QSingleton
 2 {
 3 public:
 4     static QSingleton& instance()
 5     {
 6         static QSingleton qinstance;
 7         return qinstance;
 8     }
 9 private:
10     QSingleton(){}
11     QSingleton(const QSingleton&){}
12     QSingleton& operator==(const QSingleton&){}
13 };
复制代码

  在上述单例模式的实现中,在instance函数中声明static的局部变量,因为静态变量在程序中只会分配一次内存,保证了实例的唯一性,并且作为局部变量只有在程序第一次调用的时候才会初始化,也实现了延迟加载,而且因为不是指针变量,在程序结束时会自动回收内存,几乎就是完美的实现。虽然是只分配一次内存,但就能够确保线程安全吗?答案是否定的,因为c++的构造函数本身就不是线程安全的,当我们在构造函数内部初始化成员变量或者全局变量时,时间片就有可能被切走,我们在使用时,这一点尤为重要。

posted on   一杯清酒邀明月  阅读(740)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
历史上的今天:
2020-06-22 Qt 对话框窗体关闭时,如何自动销毁窗体类对象、清空内存
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示