В случае, если вы используете ленивые синглтоны (которые возвращают статику, созданную по требованию), у вас может возникнуть возможность, когда один синглтон использует другой после того, как он уже был удален. Например, представьте, что у вас есть глобальный HttpClient
синглтон, который позволяет вам выполнять http-запросы. Кроме того, вы, вероятно, хотите иметь ведение журнала, которое может быть предоставлено Log
singleton:
class HttpClient
{
...
static HttpClient& singleton()
{
static HttpClient http;
return http;
}
};
То же самое для Log
синглтона. Теперь представьте, что конструктор и деструктор HttpClient
просто регистрируют, что этот HttpClient
был создан и удален. В этом случае деструктор HttpClient
может в конечном итоге использовать удаленный Log
singleton.
Пример кода :
#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
), которые используют друг друга в случайном порядке, у вас все равно останутся эти проблемы. В таких случаях иногда лучше выделить один раз в кучу, никогда не удаляйте некоторые из этих объектов.