Обратите внимание, что ни один из перечисленных вами примеров на самом деле не пытается удалить синглтон, и ни один из них не является поточно-ориентированным, поскольку они ничего не делают для синхронизации своих:
- сравнить с нулевым (разных вкусов)
- их размещение объекта
- их назначение указателя
Ни один из ваших существующих методов не может безопасно проверить, был ли синглтон еще создан.
C ++ 11 добавляет гарантию для члена статических данных метода, что его инициализация будет поточно-ориентированной - то есть вызываться только один раз. Это рекомендуемый механизм. Классический синглтон - это что-то вроде:
SClass& Singleton()
{
static SClass single(args);
return single;
}
Если вы действительно хотите, чтобы объект был выделен в куче:
SClass* Singleton()
{
static std::unique_ptr<SClass> single (new SClass(args));
return single;
}
[Обратите внимание, что вы можете использовать вызовы функций и / или лямбды в конструкции, если конструкция вашего объекта не является простой новой. Вы можете написать лямбду как второй аргумент std :: unique_ptr, чтобы получить сложное уничтожение, так что вы можете легко настроить ее внутри этой модели. ]
Если вы действительно хотите сделать это без использования статического члена, вы можете использовать std :: mutex или std :: atomic с cmpexchange, но в c ++ 11 статический член работает для вас.
Также объект будет уничтожен, и в порядке, обратном порядку их создания. Однако это может привести к проблемам с фениксом, когда SomeThing вызывает синглтон во время его уничтожения, и он был создан раньше, чем ваш синглтон, поэтому разрушается позже.
Версии, которые вы просматриваете, просто никогда не удаляют объект, что сделает valgrind очень несчастным. Если вам нужно ваше существующее поведение с поточно-безопасной конструкцией, но без уничтожения, используйте необработанный указатель (у них нет деструкторов):
SClass* Singleton()
{
static SClass* single = new SClass(args);
return single;
}
Обратное исправление заключается в том, чтобы SomeThing вызывал синглтон во время его создания или мог обрабатывать nullptr по возвращении. Конечно, вы должны отладить этот сценарий, прежде чем вы сможете это исправить. Поэтому я рекомендую вам, по крайней мере, добавить аварийный сигнал в ваш синглтон, чтобы вы знали, почему он не работает:
[На самом деле, как отмечено в комментариях, это не так тривиально, так как unique_ptr может не оставить себя в хорошем состоянии после уничтожения, поэтому нам нужен собственный обработчик времени жизни ptr. К счастью, нам не нужен полноценный unique_ptr, всего 3 или 4 метода. Потенциально это входит в UB, так как компилятор может оптимизировать это нулевое назначение во время уничтожения, или DEADBEEF это местоположение, но фениксы в любом случае не должны использоваться в производственном коде.]
SClass* Singleton()
{
struct PUP: std::unique_ptr<SClass> single
{
typedef std::unique_ptr<SClass> base;
PUP(SClass* s): base(s) {}
~PUP() { operator =( base()_); }
};
static PUP single (new SClass(args));
assert(single && "Singleton has been called in late static destructor - need phoenix");
return single;
}
Вы можете, если хотите, поднять объект из мертвых. Это должно быть безопасно, поскольку статическое разрушение является однопоточным. Обратите внимание, что повторно возникший объект никогда не может быть уничтожен и не сохраняет состояния старого объекта. Это может быть полезно для регистрации классов, когда кто-то добавляет запись в деструкторе, не понимая, что синглтон класса регистрации уже уничтожен. Для диагностики это полезно. Что касается производственного кода, он все равно расстроит valgrind, просто не делайте этого!
SClass* Singleton()
{
static PUP single = new SClass(args);
if (!single)
{
single = std::unique_ptr<SClass>(new SClass(args));
// Hmm, and here is another phoenix being revived:
Logger(CRITICAL) << " Revived singleton for class SClass!" << std::endl;
}
return single;
}