单例模式 c++

写在前面:文中标注序号的部分为关键部分,其实现与代码中注释序号相同,对照阅读,事半功倍。

功能和实现描述

  1. 功能:只能生成唯一一个对象的类,称为单例。该对象由单例模式唯一提供,它是基于对象的设计模式。
  2. 条件:能且只能生成一个对象。
  3. 方法:通过在类内部创建实例保证“能见代码④),又将构造函数声明为private来保证只能有类内部创建的一个实例见代码②),最后通过唯一的接口供外部资源获取该唯一实例(见代码①)。
  4. 资源释放:通过维护一个单例管理对象来控制单例对象的释放(见代码③⑤);
  5. 实现:主要有两种实现方式,实例的静态和动态创建,两者各有需要注意的地方;
  6. 本文将以实际项目工程为例子,进行讲解。

单例的静态实现

单例模式静态实现代码:

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;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值