В случае, если вы используете ленивые синглтоны (которые возвращают статику, созданную по запросу), вы можете столкнуться с возможностью, когда один синглтон использует другой после того, как он уже был удален. Например, представьте, что у вас есть глобальный HttpClient
синглтон, который позволяет вам делать HTTP-запросы. Кроме того, вы, вероятно, захотите иметь ведение журнала, которое может быть предоставлено Log
singleton:
class HttpClient
{
...
static HttpClient& singleton()
{
static HttpClient http;
return http;
}
};
То же самое для Log
синглтона. Теперь представьте, что конструктор HttpClient
и деструктор просто регистрируют, что этот HttpClient
был создан и удален. В этом случае деструктор HttpClient
может использовать удаленный синглтон Log
.
Пример кода:
#include <stdio.h>
class Log
{
Log()
{
msg("Log");
}
~Log()
{
msg("~Log");
}
public:
static Log& singleton()
{
static Log log;
return log;
}
void msg(const char* str)
{
puts(str);
}
};
class HttpClient
{
HttpClient()
{
Log::singleton().msg("HttpClient");
}
~HttpClient()
{
Log::singleton().msg("~HttpClient");
}
public:
static HttpClient& singleton()
{
static HttpClient http;
return http;
}
void request()
{
Log::singleton().msg("HttpClient::request");
}
};
int main()
{
HttpClient::singleton().request();
}
и вывод:
Log
HttpClient
HttpClient::request
~HttpClient
~Log
Пока все правильно, просто потому, что так получилось, что Log
был сконструирован раньше HttpClient
, а значит, HttpClient
еще может использовать Log
в своем деструкторе. Теперь просто закомментируйте код регистрации в конструкторе HttpClient
, и вы получите эти выходные данные :
Log
HttpClient::request
~Log
~HttpClient
Как видите, журнал используется после того, как его деструктор ~Log
уже был вызван. Как уже отмечалось, деглобализация может быть лучшим подходом, но если вы хотите использовать синглтоны, созданные по запросу, и сделать так, чтобы некоторые из них жили дольше других, вы можете сделать такие синглтоны используйте глобальную статическую инициализацию по запросу. Я довольно часто использую этот подход в рабочем коде:
class Log
{
friend std::unique_ptr<Log>::deleter_type;
...
static std::unique_ptr<Log> log;
static Log& createSingleton()
{
assert(!log);
log.reset(new Log);
return *log;
}
public:
static Log& singleton()
{
static Log& log = createSingleton();
return log;
}
};
std::unique_ptr<Log> Log::log;
Теперь, независимо от порядка создания этих синглетонов, порядок уничтожения гарантирует, что Log
будет уничтожен после HttpClient
. Однако это все равно может завершиться ошибкой и привести к неожиданному выводу, если HttpClient
использовался из глобального статического конструктора. Или, если вы хотите иметь несколько таких «супер» глобальных переменных (например, Log
и Config
), которые используют друг друга в случайном порядке, у вас все еще будет эта проблема. В таких случаях иногда лучше выделить один раз в куче, никогда не удаляя некоторые из этих объектов.
person
Pavel P
schedule
08.12.2018