我正在使用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)的回答是“是的,的确有必要!”,我想知道为什么。
1Professional C ++,第二版,作者马克·格里高(Marc Gregoire),尼古拉斯·索尔特(Nicholas A. Solter),斯科特·J·克莱珀(Scott J. Kleper)发行者:Wrox出版日期:2011年10月
最佳答案
是的,在这种情况下需要这样做。由于这本书使用了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;
};
关于c++ - 释放用于不同同步上下文的类成员,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/46488136/