Recentemente, deparei-me com a realização / implementação do padrão de design Singleton para C ++. Foi assim (eu o adotei no exemplo da vida real):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
A partir desta declaração, posso deduzir que o campo da instância é iniciado no heap. Isso significa que há uma alocação de memória. O que não está totalmente claro para mim é quando exatamente a memória será desalocada? Ou há um erro e um vazamento de memória? Parece que há um problema na implementação.
Minha principal pergunta é: como implementá-lo da maneira correta?
c++
design-patterns
singleton
Artem Barger
fonte
fonte
Respostas:
Em 2008, forneci uma implementação em C ++ 98 do padrão de design Singleton que é avaliado preguiçosamente, com destruição garantida e não é tecnicamente seguro para threads:
Alguém pode me fornecer uma amostra de Singleton em c ++?
Aqui está uma implementação atualizada do C ++ 11 do padrão de design Singleton que é avaliada preguiçosamente, destruída corretamente e segura para threads .
Consulte este artigo sobre quando usar um singleton: (não frequentemente)
Singleton: como ele deve ser usado
Consulte este dois artigos sobre ordem de inicialização e como lidar com:
Ordem de inicialização de variáveis estáticas
Localizando problemas de ordem de inicialização estática em C ++
Consulte este artigo descrevendo as vidas úteis:
Qual é a vida útil de uma variável estática em uma função C ++?
Consulte este artigo que discute algumas implicações de encadeamento para singletons: A
instância Singleton declarada como variável estática do método GetInstance, é segura para threads?
Consulte este artigo que explica por que o bloqueio com verificação dupla não funciona no C ++:
Quais são os comportamentos indefinidos comuns que um programador de C ++ deve conhecer?
Dr. Dobbs: C ++ e os perigos do bloqueio duplo: parte I
fonte
What irks me most though is the run-time check of the hidden boolean in getInstance()
Essa é uma suposição sobre a técnica de implementação. Não é necessário supor que esteja vivo. consulte stackoverflow.com/a/335746/14065 Você pode forçar uma situação para que ela esteja sempre viva (menos sobrecarga queSchwarz counter
). As variáveis globais têm mais problemas com a ordem de inicialização (entre as unidades de compilação), pois você não força uma ordem. A vantagem deste modelo é 1) inicialização lenta. 2) Capacidade de fazer cumprir uma ordem (Schwarz ajuda, mas é mais feio). Sim,get_instance()
é muito mais feio.Sendo um Singleton, você geralmente não quer que seja destruído.
Ele será desmembrado e desalocado quando o programa terminar, que é o comportamento normal e desejado para um singleton. Se você deseja limpá-lo explicitamente, é bastante fácil adicionar um método estático à classe que permita restaurá-lo para um estado limpo e realocá-lo na próxima vez em que for usado, mas isso está fora do escopo de um singleton "clássico".
fonte
Você pode evitar a alocação de memória. Existem muitas variantes, todas com problemas em caso de ambiente multithreading.
Eu prefiro esse tipo de implementação (na verdade, não se diz corretamente que prefiro, porque evito singletons o máximo possível):
Não possui alocação de memória dinâmica.
fonte
Resposta de @Loki Astari é excelente.
No entanto, há momentos com vários objetos estáticos nos quais você precisa garantir que o singleton não será destruído até que todos os seus objetos estáticos que usam o singleton não precisem mais dele.
Nesse caso,
std::shared_ptr
pode ser usado para manter o singleton ativo para todos os usuários, mesmo quando os destruidores estáticos estão sendo chamados no final do programa:fonte
Outra alternativa sem alocação: crie um singleton, digamos, de classe
C
, conforme necessário:usando
Nem esta nem a resposta de Cătălin são automaticamente seguras contra threads no C ++ atual, mas estarão no C ++ 0x.
fonte
Eu não encontrei uma implementação de CRTP entre as respostas, então aqui está:
Para usar apenas herde sua classe disso, como:
class Test : public Singleton<Test>
fonte
A solução na resposta aceita tem uma desvantagem significativa - o destruidor do singleton é chamado depois que o controle sai da
main()
função. Pode haver realmente problemas quando alguns objetos dependentes são alocados dentromain
.Encontrei esse problema ao tentar introduzir um Singleton no aplicativo Qt. Decidi que todas as minhas caixas de diálogo de configuração deveriam ser Singletons e adotou o padrão acima. Infelizmente, a classe principal do Qt
QApplication
foi alocada na pilha nomain
função, e o Qt proíbe a criação / destruição de diálogos quando nenhum objeto de aplicativo está disponível.É por isso que eu prefiro singletons alocados em heap. Eu forneço um explícito
init()
eterm()
métodos para todos os singletons e os chamo por dentromain
. Assim, eu tenho um controle total sobre a ordem de criação / destruição de singletons, e também garanto que os singletons serão criados, independentemente de alguém ligargetInstance()
ou não.fonte
Aqui está uma implementação fácil.
Somente um objeto criado e essa referência de objeto são retornados sempre e sempre.
Aqui 00915CB8 é o local da memória do objeto singleton, o mesmo para a duração do programa, mas (normalmente!) É diferente cada vez que o programa é executado.
NB Este não é um fio seguro. Você deve garantir a segurança do fio.
fonte
Se você deseja alocar o objeto na pilha, por que não usar um ponteiro exclusivo. A memória também será desalocada, pois estamos usando um ponteiro exclusivo.
fonte
m_s
a um localstatic
degetInstance()
e inicializar-lo imediatamente, sem um teste.Na verdade, é provavelmente alocado a partir da pilha, mas sem as fontes não há como saber.
A implementação típica (tirada de algum código que eu já tenho no emacs) seria:
... e confie no programa que está fora do escopo para limpar depois.
Se você trabalha em uma plataforma onde a limpeza deve ser feita manualmente, provavelmente adicionaria uma rotina de limpeza manual.
Outro problema ao fazê-lo dessa maneira é que não é seguro para threads. Em um ambiente multithread, dois threads podem passar pelo "if" antes que um deles tenha a chance de alocar a nova instância (para que ambos o fizessem). Isso ainda não é grande coisa se você estiver dependendo da finalização do programa para limpar de qualquer maneira.
fonte
Alguém mencionou
std::call_once
estd::once_flag
? A maioria das outras abordagens - incluindo o bloqueio com verificação dupla - está quebrada.Um grande problema na implementação de padrões singleton é a inicialização segura. A única maneira segura é proteger a sequência de inicialização com barreiras de sincronização. Mas essas barreiras precisam ser iniciadas com segurança.
std::once_flag
é o mecanismo para obter uma inicialização segura garantida.fonte
Analisamos esse tópico recentemente na minha classe EECS. Se você quiser ver as notas da aula em detalhes, visite http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf
Existem duas maneiras de criar uma classe Singleton corretamente.
Primeira maneira:
Implemente de maneira semelhante à do seu exemplo. Quanto à destruição, "os singletons geralmente perduram pela duração da execução do programa; a maioria dos sistemas operacionais recupera memória e a maioria dos outros recursos quando um programa é finalizado; portanto, há um argumento para não se preocupar com isso".
No entanto, é uma boa prática limpar no final do programa. Portanto, você pode fazer isso com uma classe SingletonDestructor estática auxiliar e declarar isso como amigo no seu Singleton.
O Singleton_destroyer será criado na inicialização do programa e "quando o programa terminar, todos os objetos globais / estáticos serão destruídos pelo código de desligamento da biblioteca de tempo de execução (inserido pelo vinculador); portanto, o_destroyer será destruído; seu destruidor excluirá o Singleton, executando seu destruidor."
Segunda Via
Isso é chamado de Meyers Singleton, criado pelo assistente de C ++, Scott Meyers. Simplesmente defina get_instance () de maneira diferente. Agora você também pode se livrar da variável de membro do ponteiro.
Isso é legal porque o valor retornado é por referência e você pode usar a
.
sintaxe em vez de->
acessar variáveis de membro."O compilador cria automaticamente o código que cria 's' pela primeira vez através da declaração, e não depois e exclui o objeto estático na finalização do programa."
Observe também que, com o Meyers Singleton, "você pode entrar em uma situação muito difícil se os objetos confiarem um no outro no momento do término - quando o Singleton desaparece em relação a outros objetos? Mas, para aplicativos simples, isso funciona bem".
fonte
Além da outra discussão aqui, pode ser interessante notar que você pode ter globalidade, sem limitar o uso a uma instância. Por exemplo, considere o caso de referência contando algo ...
Agora, em algum lugar dentro de uma função (como
main
), você pode fazer:Os árbitros não precisam armazenar um ponteiro de volta nos respectivos,
Store
porque essas informações são fornecidas em tempo de compilação. Você também não precisa se preocupar com aStore
vida útil do compilador, porque o compilador exige que seja global. Se, de fato, existe apenas uma instânciaStore
, não há sobrecarga nessa abordagem; com mais de uma instância, cabe ao compilador ser inteligente sobre a geração de código. Se necessário, aItemRef
classe pode até ser feitafriend
deStore
(você pode ter amigos incríveis!).Se
Store
ela própria é uma classe de modelo, as coisas ficam mais confusas, mas ainda é possível usar esse método, talvez implementando uma classe auxiliar com a seguinte assinatura:O usuário agora pode criar um
StoreWrapper
tipo (e instância global) para cadaStore
instância global e sempre acessar as lojas por meio da instância do wrapper (esquecendo os detalhes sangrentos dos parâmetros do modelo necessários para o usoStore
).fonte
Trata-se do gerenciamento do tempo de vida do objeto. Suponha que você tenha mais de singletons em seu software. E eles dependem do logger singleton. Durante a destruição do aplicativo, suponha que outro objeto singleton use o Logger para registrar suas etapas de destruição. Você deve garantir que o Logger seja limpo por último. Portanto, verifique também este documento: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
fonte
Minha implementação é semelhante à de Galik. A diferença é que minha implementação permite que os ponteiros compartilhados limpem a memória alocada, em vez de manter a memória até que o aplicativo seja encerrado e os ponteiros estáticos sejam limpos.
fonte
Seu código está correto, exceto que você não declarou o ponteiro da instância fora da classe . As declarações de classe estática de variáveis estáticas não são consideradas declarações em C ++, no entanto, isso é permitido em outras linguagens como C # ou Java etc.
Você deve saber que a instância Singleton não precisa ser excluída manualmente por nós . Precisamos de um único objeto em todo o programa; assim, no final da execução do programa, ele será desalocado automaticamente.
fonte
O documento que foi vinculado acima descreve a falha do bloqueio com verificação dupla: o compilador pode alocar a memória para o objeto e definir um ponteiro para o endereço da memória alocada, antes que o construtor do objeto seja chamado. No entanto, é bastante fácil no c ++ usar alocadores para alocar a memória manualmente e, em seguida, usar uma chamada de construção para inicializar a memória. Usando essa abordagem, o bloqueio verificado duas vezes funciona perfeitamente.
fonte
Exemplo:
fonte
Classe singleton simples, este deve ser seu arquivo de classe de cabeçalho
Acesse seu singleton assim:
fonte
Eu acho que você deve escrever uma função estática em que seu objeto estático é excluído. Você deve chamar esta função quando estiver prestes a fechar seu aplicativo. Isso garantirá que você não tenha vazamento de memória.
fonte