Je découvre le modèle de conception singleton avec le chapitre 29 de Professional C++, deuxième édition . Il illustre une implémentation singleton d'une classe Logger
qui couvre les exigences de sécurité des threads:Libération de membres de classe utilisés dans différents contextes de synchronisation
tête
#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;
};
mise en œuvre
#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;
}
Il poursuit en expliquant pourquoi la classe ami Cleanup
est introduit:
Le
Cleanup
classe est là pour vous assurer que l'instanceLogger
unique est supprimé correctement à l'arrêt du programme. Ceci est nécessaire car cette implémentation alloue dynamiquement l'instanceLogger
par en utilisant le nouvel opérateur dans un bloc de code protégé par un mutex. Une instance statique de la classeCleanup
sera créée la première fois que la méthode instance() s'appellera . Lorsque le programme se termine, l'exécution C++ va détruire cette instanceCleanup
statique, ce qui déclenchera la suppression de l'objetLogger
et un appel au destructeurLogger
pour fermer le fichier.
Je trouve très déroutant qu'il déclare « Cela est nécessaire parce que ... », comme s'il n'y avait pas d'autre alternative.
Mes questions:
1) Est-il vraiment nécessaire? Ne serait-il suffisant d'avoir juste toutes les manipulations dans le destructor ?, comme:
Logger::~Logger()
{
{
lock_guard<mutex> guard(Logger::sMutex);
delete Logger::pInstance;
Logger::pInstance = nullptr;
}
mOutputStream.close();
}
2) Si la réponse à 1) est, je voudrais savoir « oui, il est en effet nécessaire! » Pourquoi.
Professional C++, deuxième édition par Marc Grégoire, Nicholas A. Solter, Scott J. Kleper Éditeur: Wrox Date de publication: Octobre 2011
Qu'est ce qui déclencherait le destructeur de 'Logger'? – Mat
Toujours libérer ce que vous allouez, ou vous auriez une fuite de ressources. Rappelez-vous que tous les systèmes d'exploitation ne le font pas pour vous. –
BTW, utilisez Singers Singleton et vous n'aurez pas besoin de cela (l'instance n'est pas un pointeur). – Jarod42