C++ 单例设计模式

有些类只需要有一个实例,比如软件开发过程中的日志功能,因为我们要频繁的写log,不可能每次都要重新new一个日志实例,然后用完再delete。当然在C++中可以用全局变量来代替,但全局变量有太多的不可预测性,特别是在多线程的开发中,所以对全局变量我们是能不用就不用。此时单例设计模式就派上用场了。先上代码,在分析为何这么写。

// Singleton.h
#pragma once
#include <windows.h>

class CSingleton
{
public:
    static CSingleton* GetInstance();
    bool WriteLog(const TCHAR* szLogPath, const TCHAR* szLogInfo);
private:
    CSingleton(){}; //
    ~CSingleton(){};
    CSingleton(const CSingleton&);
    CSingleton& operator=(const CSingleton&);
    static CSingleton* m_Instance;
    static HANDLE m_pSingleMutex;
    class CGarbo
    {
    public:
        ~CGarbo()
        {
            if (m_Instance != NULL)
            {
                WaitForSingleObject(m_pSingleMutex, INFINITE);
                if (m_Instance != NULL)
                {
                    delete m_Instance;
                    m_Instance = NULL;
                }
                ReleaseMutex(m_pSingleMutex);
            }
            if (m_pSingleMutex != NULL)
            {
                CloseHandle(m_pSingleMutex);
                m_pSingleMutex = NULL;
            }
        }
    };
    static CGarbo m_Garbo;
};

单例模式的定义: 保证一个类只有一个实例,并提供一个可供全局访问的访问点。

  1. m_Instance是类的唯一实例,把它申明成静态私有变量,通过公有函数GetInstance返回
  2. 类要创建实例需调用类的构造函数,为了防止类外部创建类的实例,把类的构造函数申明私有
  3. CGarbo 用来进行资源释放
    程序运行结束时,系统会自动调用静态成员m_Garbo的析构函数,该析构函数进行资源释放,这种资源的释放是在程序员不知道的情况下进行的;
    —— 程序运行结束时,系统会自动析构所有的全局变量,我们知道全局变量和静态变量都储存在静态存储区,所以在析构时,也会析构静态成员变量
// Singleton.cpp
#include "stdafx.h"
#include "Singleton.h"
#include <fstream>

using namespace std;


CSingleton* CSingleton::m_Instance = NULL;
CSingleton::CGarbo CSingleton::m_Garbo;
HANDLE CSingleton::m_pSingleMutex = CreateMutex(NULL, FALSE, NULL);

CSingleton* CSingleton::GetInstance()
{
    if (m_Instance == NULL)
    {
        WaitForSingleObject(m_pSingleMutex, INFINITE);
        if (m_Instance == NULL)
        {
            m_Instance = new CSingleton();
        }
        ReleaseMutex(m_pSingleMutex);
    }
    return m_Instance;
}

bool CSingleton::WriteLog(const TCHAR* szLogPath, const TCHAR* szLogInfo)
{
    FILE * pLogFile;
    int nRtn = -1;
    _tfopen_s(&pLogFile, szLogPath, _T("a"));
    if (pLogFile == NULL)
    {
        return false;
    }
    nRtn = _ftprintf(pLogFile, _T("%s\n"), szLogInfo);
    if (nRtn < 0)
    {
        fflush(pLogFile);
        fclose(pLogFile);
        return false;
    }
    fflush(pLogFile);
    fclose(pLogFile);
    return true;
}

GetInstance()代码分析:

// 普通单例实例化,线程不安全
// 如果多个线程同时调用GetInstance(),假设其中A线程执行完1后且此时m_Instance=NULL,该线程挂起,
// 然后当B线程调用GetInstance()时,m_Instance=NULL仍为空,B线程会创建CSingleton的一个实例
// 某个时刻当A线程继续执行时,由于之前1语句已经执行完,故A线程也会创建CSingleton的一个实例,这和单例模式的初衷是不符合的

CSingleton* CSingleton::GetInstance()
{
    if (m_Instance == NULL)/*1*/
    {
        m_Instance = new CSingleton();/*2*/
    }
    return m_Instance;
}
// 为了线程安全问题,在创建实例的时候加锁,创建完后释放
// 当有多个线程调用GetInstance()时,每次都要加锁解锁,频繁的锁操作对性能是有影响的
// 并且当多个线程同时调用GetInstance()时,只有一个线程能够继续执行,其他线程要先等待,同样对性能也有影响

CSingleton* CSingleton::GetInstance()
{
    WaitForSingleObject(m_pSingleMutex, INFINITE);/*Lock*/
    if (m_Instance == NULL)
    {
        m_Instance = new CSingleton();
    }
    ReleaseMutex(m_pSingleMutex);/*UnLock*/
    return m_Instance;
}

// 为了解决上述问题,引入了“双锁检”机制(1 And 3)
// 当有多个线程调用GetInstance()时,如果m_Instance为NULL,GetInstance()直接返回,既不用频繁的锁操作,线程也不用等待
// 那为什么要有3了?  原因和普通单例线程不安全的原因相同

CSingleton* CSingleton::GetInstance()
{
    if (m_Instance == NULL)/*1*/
    {
        WaitForSingleObject(m_pSingleMutex, INFINITE);/*2*/
        if (m_Instance == NULL)/*3*/
        {
            m_Instance = new CSingleton();
        }
        ReleaseMutex(m_pSingleMutex);
    }
    return m_Instance;
}

Last

// main 函数中调用
CSingleton *singleObj = CSingleton::GetInstance();
singleObj->WriteLog(_T("D:\\Single.log"), _T("info000000"));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值