C++单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全
局访问点,该实例被所有程序模块共享。

为什么要有单例模式?
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统等等。
在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

单例模式通常有两种实现方式:饿汉模式和懒汉模式。

1. 饿汉模式

就是不管你是否使用,程序启动时就创建一个唯一的实例对象

class Singleton
 {
 public:
 static Singleton* GetInstance()    //要点三
 {
 return &m_instance;
 }
 
 private:
 // 构造函数私有   要点一
 Singleton(){};
 
 // C++98 防拷贝
 Singleton(Singleton const&); 
 Singleton& operator=(Singleton const&); 
 
 // or
 
 // C++11
 Singleton(Singleton const&) = delete; 
 Singleton& operator=(Singleton const&) = delete; 
 
 static Singleton m_instance;   //要点二
 };
 
 Singleton Singleton::m_instance; // 在程序入口之前就完成单例对象的初始化

我们可以看到饿汉模式满足了上述的三个要点,因为是在程序启动时就创建好的实例对象,所以是线程安全的。同样也会优缺点,那就是程序启动比较慢。

2. 懒汉模式

懒汉模式就是在你第一次使用时,我才创建这个对象。

因为是在使用时才创建对象,有可能存在线程安全问题,进程启动时无负载,比较快速。

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
class Singleton
{
public:
 static Singleton* GetInstance() {
 // 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
     if (nullptr == m_pInstance) {
         m_mtx.lock();
         if (nullptr == m_pInstance) {
            m_pInstance = new Singleton();
         }
         m_mtx.unlock();
     }
     return m_pInstance;
 }
 // 实现一个内嵌垃圾回收类 
 class CGarbo {
 public:
     ~CGarbo(){
         if (Singleton::m_pInstance)
             delete Singleton::m_pInstance;
     }
 };
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
 static CGarbo Garbo;
private:
 // 构造函数私有
 Singleton(){};
 // 防拷贝
 Singleton(Singleton const&);
 Singleton& operator=(Singleton const&);
 static Singleton* m_pInstance; // 单例对象指针
 static mutex m_mtx; //互斥锁
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;
void func(int n)
{
 cout<< Singleton::GetInstance() << endl;
}
// 多线程环境下演示
int main()
{
 thread t1(func, 10);
 thread t2(func, 10);
 t1.join();
 t2.join();
 cout << Singleton::GetInstance() << endl;
 cout << Singleton::GetInstance() << endl;
}

为什么要使用双重检查?
假设我只有一个检查语句,在多线程的环境下运行程序,有可能在同一时刻多个线程同时满足检查条件,进入 if 语句,当其中一个线程获得互斥锁之后,创建对象,解锁之后,其他进入 if 条件的线程同样会获得互斥锁,然后创建对象。所以这种情况就不满足只创建一个对象的初衷了,不是线程安全的。
但是有两个检查语句后,在多线程环境下运行程序,同样的在同一时间呢,有可能多个线程满足最外面的检查语句,进入 if 语句,当其中第一个线程获得互斥锁之后,再进行检查,此时同样满足 if 语句 nullptr == m_pInstance 创建对象之后,解锁。其他进程同样获得互斥锁,但是现在已经不满足第二个检查语句 nullptr == m_pInstance 了。所以不创建对象。 这样,通过双重检查,即可满足只创建一个对象的初衷。 能保证线程安全。

最后,程序结束时,会释放 CGarbo  对象,从而调用 CGarbo 析构函数,在CGarbo 析构函数中,我们释放掉创建的实例对象。 

 

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值