c++ - 释放在不同同步上下文中使用的类成员

标签 c++ multithreading memory-management mutex

我正在学习专业 C++ 第二版第 29 章的单例设计模式1。 它说明了涵盖线程安全要求的 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:

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)的答案是“是的,确实有必要!”,我想知道为什么。

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

最佳答案

是的,在这种情况下需要这样做。由于本书使用了 new 并分发了一个指针,所以没有对象会超出范围导致析构函数触发。这样做的唯一方法是在该指针的某处调用 deleteCleanup 类不是要求您这样做。

如果您使用 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/

相关文章:

c++ - 我把这个 if 语句放在哪里有什么不同吗?

c++ - AVL Tree Children 项目的段错误

java - 带有时间戳的 StacktraceElement

c - 如何在单独的线程中传递 blt vector

delphi - 压缩并发送 TFDDataset 的数据后释放 Form 时的 AV

Python 内存释放

c++ - strstr 在 codeforces 上的 C++ 4.7 中不起作用

c++ - MFC 单选按钮组消息处理程序

c# - 等待一个漫长的过程,还在更新UI

c# - 如何检测窗体上的所有控件使用了多少内存?