Como a implementação da Meyers de um Singleton é na verdade um Singleton?

Eu tenho lido muito sobre Singletons, quando devem e não devem ser usados, e como implementá-los com segurança. Eu estou escrevendo em C + + 11, e me deparei com a implementação inicializada de um singleton preguiçoso de Meyer, como visto nesta pergunta.

Essa implementação é:

static Singleton& instance() { static Singleton s; return s; } 

Eu entendo como isso é thread safe de outras questões aqui em SO, mas o que eu não entendo é como este é realmente um padrão singleton. Eu implementei singletons em outras linguagens, e isso sempre acaba parecido com este exemplo da Wikipedia :

 public class SingletonDemo { private static volatile SingletonDemo instance = null; private SingletonDemo() { } public static SingletonDemo getInstance() { if (instance == null) { synchronized (SingletonDemo .class){ if (instance == null) { instance = new SingletonDemo (); } } } return instance; } } 

Quando olho para esse segundo exemplo, é muito intuitivo como isso é um singleton, já que a class contém uma referência a uma instância de si mesma e só retorna essa instância. No entanto, no primeiro exemplo, não entendo como isso impede a existência de duas instâncias do object. Então minhas perguntas são:

  1. Como a primeira implementação impõe um padrão singleton? Presumo que tenha a ver com a palavra-chave estática, mas espero que alguém possa explicar-me em profundidade o que está acontecendo sob o capô.
  2. Entre esses dois estilos de implementação, um é preferível ao outro? Quais são os prós e contras?

Obrigado por qualquer ajuda,

Este é um singleton porque a duração do armazenamento static para uma function local significa que apenas uma instância desse local existe no programa.

Sob o capô, isso pode ser mais ou menos considerado equivalente ao seguinte C ++ 98 (e pode até ser implementado vagamente como este por um compilador):

 static bool __guard = false; static char __storage[sizeof(Singleton)]; // also align it Singleton& Instance() { if (!__guard ) { __guard = true; new (__storage) Singleton(); } return *reinterpret_cast(__storage); } // called automatically when the process exits void __destruct() { if (__guard) reinterpret_cast(__storage)->~Singleton(); } 

Os bits de segurança do fio fazem com que fique um pouco mais complicado, mas é essencialmente a mesma coisa.

Olhando para uma implementação real para o C ++ 11, existe uma variável de guarda para cada estática (como o booleano acima), que também é usada para barreiras e encadeamentos. Veja a saída AMD64 da Clang para:

 Singleton& instance() { static Singleton instance; return instance; } 

A assembly AMD64, por instance do Clang 3.0 do Ubuntu na AMD64 em -O1 (cortesia de http://gcc.godbolt.org/ é:

 instance(): # @instance() pushq %rbp movq %rsp, %rbp movb guard variable for instance()::instance(%rip), %al testb %al, %al jne .LBB0_3 movl guard variable for instance()::instance, %edi callq __cxa_guard_acquire testl %eax, %eax je .LBB0_3 movl instance()::instance, %edi callq Singleton::Singleton() movl guard variable for instance()::instance, %edi callq __cxa_guard_release .LBB0_3: movl instance()::instance, %eax popq %rbp ret 

Você pode ver que ele faz referência a uma proteção global para ver se a boot é necessária, usa __cxa_guard_acquire , testa a boot novamente e assim por diante. Exatamente em quase todos os aspectos, como a versão que você postou da Wikipedia, exceto usando o conjunto AMD64 e os símbolos / layout especificados na ITI ABI .

Observe que, se você executar esse teste, deverá fornecer ao Singleton um construtor não trivial, de modo que não seja um POD; caso contrário, o otimizador perceberá que não faz sentido fazer todo esse trabalho de proteção / bloqueio.

 // Singleton.hpp class Singleton { public: static Singleton& Instance() { static Singleton S; return S; } private: Singleton(); ~Singleton(); }; 

Essa implementação é conhecida como Singleton de Meyers. Scott Meyers diz:

“Essa abordagem é baseada na garantia do C ++ de que objects estáticos locais são inicializados quando a definição do object é encontrada pela primeira vez durante uma chamada para essa function.” … “Como um bônus, se você nunca chamar uma function emulando um object estático não local, você nunca incorrerá no custo de construir e destruir o object.”

Quando você chama Singleton& s=Singleton::Instance() primeira vez, o object é criado e cada próxima chamada para Singleton::Instance() resulta com o mesmo object sendo retornado. Problema principal:

  • sujeito a Fiasco de Ordem de Destruição (o equivalente ao Fiasco de Ordem de Inicialização )

Outra implementação é chamada de singleton com vazamento confiável.

 class Singleton { public: static Singleton& Instance() { if (I == nullptr) { I = new Singleton(); } return *I; } private: Singleton(); ~Singleton(); static Singleton* I; }; // Singleton.cpp Singleton* Singleton::I = 0; 

Dois problemas:

  • vazamentos, a menos que você implemente um Release e certifique-se de chamá-lo (uma vez)
  • não é seguro