写在前面:文中标注序号的部分为关键部分,其实现与代码中注释序号相同,对照阅读,事半功倍。
功能和实现描述
- 功能:只能生成唯一一个对象的类,称为单例。该对象由单例模式唯一提供,它是基于对象的设计模式。
- 条件:能且只能生成一个对象。
- 方法:通过在类内部创建实例保证“能”(见代码④),又将构造函数声明为private来保证只能有类内部创建的一个实例(见代码②),最后通过唯一的接口供外部资源获取该唯一实例(见代码①)。
- 资源释放:通过维护一个单例管理对象来控制单例对象的释放(见代码③⑤);
- 实现:主要有两种实现方式,实例的静态和动态创建,两者各有需要注意的地方;
- 本文将以实际项目工程为例子,进行讲解。
单例的静态实现
单例模式静态实现代码:
Logger.h
/******************************
单例的静态实现方法
******************************/
#ifndef LOGGER_H
#define LOGGER_H
#include <iostream>
#include <fstream>
#include <time.h>
using namespace std;
namespace Framework
{
enum LOGTYPE
{
INFO = 0,
WARNING,
ERROR
};
}
class Logger
{
public:
static Logger *getInstance(); // ①返回唯一实例对象,获取实例的唯一接口
void setPath(const char* absFile);
bool write(char* flag, char* content, Framework::LOGTYPE logType = Framework::LOGTYPE::INFO); // 业务功能:写日志
void flush(); // 业务功能:清空缓冲区, 手动刷新,如果不调用,会在释放logger时自动调用
~VisaLogger();
private:
void setPreFix(char *flag); // 格式化输出头
Logger(); // ②私有化构造函数
class Manger // ③单例管理类,用来在程序结束时自动释放单例对象
{
public:
Manger(){}
~Manger()
{
// 管理类在自身销毁时释放单例对象
if(nullptr != logger)
{
delete logger;
logger = nullptr;
}
}
};
private:
static VisaLogger* logger; // ④注意这里的两个变量的初始化
static Manger logManger; // ⑤如果将该处放在InstanceManger内部,在此处进行定义,可以进构造,但无法正常析构,所以静态变量,一定要放在类外
ofstream fout;
char m_path[512];
};
#endif // LOGGER_H
Logger.cpp
#include "Logger.h"
#include <iomanip>
#include <assert.h>
using namespace Visa;
Logger::Manger Logger::logManger;
Logger* Logger::logger = new Logger; // 一定要在类外完成定义,否则能构造,但不析构
Logger *Logger::getInstance()
{
return logger==nullptr? new Logger:logger;
}
void Logger::setPath(const char *absFile)
{
cout<<"absFile:"<< absFile<<endl;
fout.open(absFile,ios::out | ios::app);
assert(fout.is_open());
}
bool Logger::write(char *flag, char *content, LOGTYPE logType)
{
// 根据logType创建保存到不同类型的文件中
setPreFix(flag);
fout<< content <<"\n";
return true;
}
void Logger::flush()
{
fout.flush();
}
Logger::~Logger()
{
fout.flush();
if(!fout)
{
fout.clear();
fout.close();
}
}
void Logger::setPreFix(char *flag)
{
time_t timer;
struct tm *tblock;
timer=time(NULL);
tblock=localtime(&timer);
fout<< tblock->tm_year+1900 <<"-"<< tblock->tm_mon+1<<"-"\
<< setw(2)<<setfill('0')<< tblock->tm_mday<<" "\
<< setw(2)<<setfill('0')<< tblock->tm_hour<<":"\
<< setw(2)<<setfill('0')<< tblock->tm_min<<":"\
<< setw(2)<<setfill('0')<< tblock->tm_sec<<" "\
<< setw(4)<<setfill(' ')<< flag;
}
Logger::Logger()
{
cout<<"Logger"<<endl;
}
单例的动态实现
单例的动态实现与静态实现的区别是在外部第一次调用getInstance接口时才会创建唯一实例,需要注意以下三点:
- 仅创建一次单例对象,但实际开发中getInstance接口会被反复调用,因此需要添加条件判断,保证只在第一次调用时创建,其后的操作只是返回已创建的实例;⑥
- 由于上面创建实例的约束,使得创建实例操作不能保证原子化,因此在多线程调用时,容易出现创建多个实例对象的情况,因此需要注意线程安全;⑦
- 在保证线程安全时,会添加互斥锁,上锁和解锁算是比较耗费资源的操作,应保证互斥锁在不需要创建实例时不需要操作。 ⑦
其实现与静态成员的实现只在实例是否是静态创建上,下面是相关实现代码,其中④处的声明,⑥⑦处的实例创建为代码上的区别,其余部分相同。
/******************************
单例的动态实现方法
******************************/
#ifndef LOGGER_H
#define LOGGER_H
#include <iostream>
#include <fstream>
#include <time.h>
using namespace std;
namespace Framework
{
enum LOGTYPE
{
INFO = 0,
WARNING,
ERROR
};
}
class Logger
{
public:
static Logger *getInstance(); // ①返回唯一实例对象,获取实例的唯一接口
void setPath(const char* absFile);
bool write(char* flag, char* content, Framework::LOGTYPE logType = Framework::LOGTYPE::INFO); // 业务功能:写日志
void flush(); // 业务功能:清空缓冲区, 手动刷新,如果不调用,会在释放logger时自动调用
~VisaLogger();
private:
void setPreFix(char *flag); // 格式化输出头
Logger(); // ②私有化构造函数
class Manger // ③单例管理类,用来在程序结束时自动释放单例对象
{
public:
Manger(){}
~Manger()
{
// 管理类在自身销毁时释放单例对象
if(nullptr != logger)
{
delete logger;
logger = nullptr;
}
}
};
private:
VisaLogger* logger; // ④注意这里的两个变量的初始化
static Manger logManger; // ⑤如果将该处放在InstanceManger内部,在此处进行定义,可以进构造,但无法正常析构,所以静态变量,一定要放在类外
ofstream fout;
char m_path[512];
};
#endif // LOGGER_H
cpp
#include "Logger.h"
#include <iomanip>
#include <assert.h>
using namespace Visa;
Logger::Manger Logger::logManger;
Logger *Logger::getInstance()
{
if(nullptr != logger)
{
mutex.lock(); // ⑥非 nullptr 才需要上锁
if(nullptr != logger) // ⑦在判nullptr后、lock前,logger可能在其它线程已创建,所以要二次判nullptr保证原子性
{
logger = new Logger;
}
}
}
void Logger::setPath(const char *absFile)
{
cout<<"absFile:"<< absFile<<endl;
fout.open(absFile,ios::out | ios::app);
assert(fout.is_open());
}
bool Logger::write(char *flag, char *content, LOGTYPE logType)
{
// 根据logType创建保存到不同类型的文件中
setPreFix(flag);
fout<< content <<"\n";
return true;
}
void Logger::flush()
{
fout.flush();
}
Logger::~Logger()
{
fout.flush();
if(!fout)
{
fout.clear();
fout.close();
}
}
void Logger::setPreFix(char *flag)
{
time_t timer;
struct tm *tblock;
timer=time(NULL);
tblock=localtime(&timer);
fout<< tblock->tm_year+1900 <<"-"<< tblock->tm_mon+1<<"-"\
<< setw(2)<<setfill('0')<< tblock->tm_mday<<" "\
<< setw(2)<<setfill('0')<< tblock->tm_hour<<":"\
<< setw(2)<<setfill('0')<< tblock->tm_min<<":"\
<< setw(2)<<setfill('0')<< tblock->tm_sec<<" "\
<< setw(4)<<setfill(' ')<< flag;
}
Logger::Logger()
{
cout<<"Logger"<<endl;
}