我正在通过Professional C ++第二章 1 的第29章学习单例设计模式。
它说明了Logger
类的单例实现,它涵盖了线程安全要求:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <mutex>
// Definition of a multithread safe singleton logger class
class Logger
{
public:
static const std::string kLogLevelDebug;
static const std::string kLogLevelInfo;
static const std::string kLogLevelError;
// Returns a reference to the singleton Logger object
static Logger& instance();
// Logs a single message at the given log level
void log(const std::string& inMessage,
const std::string& inLogLevel);
// Logs a vector of messages at the given log level
void log(const std::vector<std::string>& inMessages,
const std::string& inLogLevel);
protected:
// Static variable for the one-and-only instance
static Logger* pInstance;
// Constant for the filename
static const char* const kLogFileName;
// Data member for the output stream
std::ofstream mOutputStream;
// Embedded class to make sure the single Logger
// instance gets deleted on program shutdown.
friend class Cleanup;
class Cleanup
{
public:
~Cleanup();
};
// Logs message. The thread should own a lock on sMutex
// before calling this function.
void logHelper(const std::string& inMessage,
const std::string& inLogLevel);
private:
Logger();
virtual ~Logger();
Logger(const Logger&);
Logger& operator=(const Logger&);
static std::mutex sMutex;
};
#include <stdexcept>
#include "Logger.h"
using namespace std;
const string Logger::kLogLevelDebug = "DEBUG";
const string Logger::kLogLevelInfo = "INFO";
const string Logger::kLogLevelError = "ERROR";
const char* const Logger::kLogFileName = "log.out";
Logger* Logger::pInstance = nullptr;
mutex Logger::sMutex;
Logger& Logger::instance()
{
static Cleanup cleanup;
lock_guard<mutex> guard(sMutex);
if (pInstance == nullptr)
pInstance = new Logger();
return *pInstance;
}
Logger::Cleanup::~Cleanup()
{
lock_guard<mutex> guard(Logger::sMutex);
delete Logger::pInstance;
Logger::pInstance = nullptr;
}
Logger::~Logger()
{
mOutputStream.close();
}
Logger::Logger()
{
mOutputStream.open(kLogFileName, ios_base::app);
if (!mOutputStream.good()) {
throw runtime_error("Unable to initialize the Logger!");
}
}
void Logger::log(const string& inMessage, const string& inLogLevel)
{
lock_guard<mutex> guard(sMutex);
logHelper(inMessage, inLogLevel);
}
void Logger::log(const vector<string>& inMessages, const string& inLogLevel)
{
lock_guard<mutex> guard(sMutex);
for (size_t i = 0; i < inMessages.size(); i++) {
logHelper(inMessages[i], inLogLevel);
}
}
void Logger::logHelper(const std::string& inMessage,
const std::string& inLogLevel)
{
mOutputStream << inLogLevel << ": " << inMessage << endl;
}
它继续解释为什么介绍朋友类Cleanup
:
Cleanup
类用于确保单个Logger
实例 在程序关闭时正确删除。这是必要的,因为 此实现通过动态分配Logger
实例 在使用互斥锁保护的代码块中使用new运算符。一个 第一次创建Cleanup
类的静态实例 调用instance()方法。当程序终止时,C ++ 运行时将销毁这个将触发的静态Cleanup
实例 删除Logger
对象和调用Logger
析构函数 关闭文件。
我发现它非常令人困惑,它声明“这是必要的,因为... ”,好像没有其他选择。
我的问题:
1)真的有必要吗?仅仅在析构函数中进行所有处理是不够的,例如:
Logger::~Logger()
{
{
lock_guard<mutex> guard(Logger::sMutex);
delete Logger::pInstance;
Logger::pInstance = nullptr;
}
mOutputStream.close();
}
2)如果1)的答案是“是的,那确实是必要的!”,我想知道原因。
1 专业C ++,第二版由Marc Gregoire,Nicholas A. Solter,Scott J. Kleper发布者:Wrox发布日期:2011年10月
答案 0 :(得分:1)
是的,在这种情况下需要这样做。由于本书使用了new
并分发了指针,因此没有任何对象会超出范围,导致析构函数触发。唯一的方法是在该指针的某处调用delete
。 而不是要求你这样做,而是创建了Cleanup
类。
如果您使用Meyers Singleton,可以避免所有这些。它使用单例类型的静态变量并返回指向它的指针/引用。与书籍版本不同,这将在程序结束时自动销毁。 Meyers Singleton看起来像:
class Singleton {
public:
static Singleton* Instance() { static Singleton s; return &s; }
Singleton(const Singleton&) = delete;
void operator=(const Singleton&) = delete;
private:
Singleton() = default;
};