释放在不同同步上下文中使用的 class 个成员

Releasing class members used in different synchronization contexts

我正在学习专业 C++ 第二版第 29 章的单例设计模式1。 它说明了涵盖线程安全要求的 Logger class 的单例实现:

页眉

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

接着解释为什么介绍朋友classCleanup

The Cleanup class is there to make sure the single Logger instance gets deleted properly on program shutdown. This is necessary because this implementation is dynamically allocating the Logger instance by using the new operator in a block of code protected with a mutex. A static instance of the Cleanup class will be created the first time the instance() method is called. When the program terminates, the C++ runtime will destroy this static Cleanup instance, which will trigger the deletion of the Logger object and a call to the Logger destructor to close the file.

我觉得很困惑,它说“这是必要的,因为...”,好像别无选择。

我的问题:

1) 真的有必要吗?仅在析构函数中进行所有处理是否就足够了?例如:

Logger::~Logger()
{
    {
        lock_guard<mutex> guard(Logger::sMutex);
        delete Logger::pInstance;
        Logger::pInstance = nullptr;
    }
    mOutputStream.close();
}

2)如果1)的答案是"yes, it is indeed necessary!",我想知道为什么。

1Professional C++,第二版,作者:Marc Gregoire、Nicholas A. Solter、Scott J. Kleper 出版商:Wrox 出版日期:2011 年 10 月

是的,在这种情况下需要这样做。由于本书使用 new 并分发了一个指针,因此没有对象会超出范围导致析构函数触发。这样做的唯一方法是在该指针的某处调用 deleteCleanup class 不是要求你这样做的,而是为了做到这一点而创建的。

如果使用 Meyers Singleton,所有这些都可以避免。它使用单例类型的静态变量和 returns a pointer/reference 。与书本版本不同,它会在程序结束时自动销毁。 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;
};