有些类只需要有一个实例,比如软件开发过程中的日志功能,因为我们要频繁的写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;
};
单例模式的定义: 保证一个类只有一个实例,并提供一个可供全局访问的访问点。
- m_Instance是类的唯一实例,把它申明成静态私有变量,通过公有函数GetInstance返回
- 类要创建实例需调用类的构造函数,为了防止类外部创建类的实例,把类的构造函数申明私有
- 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"));