我需要帮助来了解我的静态单例类的实现是否有效或被认为是最佳实践。
我目前正在开发一个业余爱好者 OpenGL 引擎,并且我有多个用于系统的单例类,例如日志记录、分析等,并且我在整个引擎和依赖于该引擎的应用程序中静态使用这些类。
我看过这个answer,它讨论了单例类的实现。
以下是我最初的做法:
-- Log.h --
#pragma once
#include <assert.h>
class Log {
public:
static Log& Init() {
if (!s_Instance) // EDIT 1: Forgot to add this to the post.
s_Instance = new Log();
return *s_Instance;
}
static Log& Get() {
assert(s_Instance, "Logger not initialised!");
return *s_Instance;
}
// Delete copy assignment and move assignment constructors
Log(const Log&) = delete;
Log(Log&&) = delete;
// Delete copy assignment and move assignment operators
Log& operator=(const Log&) = delete;
Log& operator=(Log&&) = delete;
private:
Log() {
}
static Log* s_Instance;
};
-- 日志.cpp --
#include "Log.h"
Log* Log::s_Instance = nullptr;
正如您从我的代码中看到的,我希望能够确定程序中何时使用静态 Init 函数初始化此类。我会更好地通过上面链接的答案采用建议的方法,还是这被认为同样适合我的目的?
对于每个考虑使用全局单例的人来说,不要这样做。 这对单元测试来说是不利的!
这是一个如何完全避免使用依赖注入来完全避免需要全局单例变量来进行日志记录的示例。
#include <iostream>
#include <string>
#include <string_view>
// assume you have a bit of code that needs to log something
// In this example I made a temperature sensor that needs to do some logging
// but the temperature sensor doesn't want to know anything about the logging system
// In that case make an interface that allows your temperature sensor to report something.
class temparature_sensor_report_itf
{
public:
virtual ~temparature_sensor_report_itf() = default;
virtual void report_temparature(int temp) = 0;
virtual void report_too_hot() = 0;
};
// Now you can write your code to use this interface
class logger_t
{
public:
// The initialization of the logger is to be done
// using a constructor
explicit logger_t(std::string_view filename)
: m_filename{filename}
{
}
void log(std::string_view msg)
{
std::cout << "Logging: " << msg << "\n";
}
private:
std::string m_filename;
};
// now make an adapter from report_itf to a specific logger_t
// this allows you to change the logger_t without changing the
// code that uses the temparature_sensor_report_itf (the temperature sensor class)
class temparature_sensor_logger_t :
public temparature_sensor_report_itf
{
public:
temparature_sensor_logger_t(logger_t& logger)
: m_logger{logger}
{
}
void report_temparature(int temp) override
{
m_logger.log("Temperature: " + std::to_string(temp));
}
void report_too_hot() override
{
m_logger.log("Too hot!");
}
private:
logger_t& m_logger;
};
// For unit testing you can now also make a reporter class that does nothing at all
class temperature_sensor_report_nothing_t :
public temparature_sensor_report_itf
{
public:
void report_temparature(int temp) override
{
// do nothing
}
void report_too_hot() override
{
// do nothing
}
};
// The temperature sensor class doesn't know anything about the logger
// it only has an injected dependency to the temparature_sensor_report_itf
class temparature_sensor_t
{
public:
explicit temparature_sensor_t(temparature_sensor_report_itf& reporter)
: m_sensor_reporter{reporter}
{
}
void read_temparature()
{
// read the temperature
int temp = 42;
// report the temperature
// but you don't know anything about the logger here.
m_sensor_reporter.report_temparature(temp);
if (temp > 40)
{
m_sensor_reporter.report_too_hot();
}
}
private:
temparature_sensor_report_itf& m_sensor_reporter;
};
int main()
{
// do NOT make a global logger initialize your logging system in main
// this also allows the destructor of your logger to do nice things
// like flushing the log file and closing it (something you cannot do
// when you use a singleton with a new but no delete)
logger_t logger{"log.txt"};
// now create a small adapter, this adapter allows you to change
// the logging infrastructure later without ever needing
// to change anything in the temparature_sensor_t class
temparature_sensor_logger_t reporter{logger};
// In unit tests you use this line instead of the previous one
// And now you can test without for example depending on the filestystem
// temperature_sensor_report_nothing_t reporter;
// now create the temparature sensor
// by injecting a class derived from temparature_sensor_report_itf
// For unit testing we will inject the temperature_sensor_report_nothing_t instance
temparature_sensor_t sensor{reporter};
// now read the temperature, if something needs to be reported
// it will be reported to the logger
sensor.read_temparature();
return 0;
}