Pergunta

Recentemente eu esbarrei em uma realização / implementação do padrão de design Singleton para C ++. Ele olhou como este (eu adotei-o do 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 de instância é iniciada na pilha. Isso significa que há uma alocação de memória. O que é completamente claro para mim é quando exatamente a memória vai ser desalocada? Ou há um vazamento de bug e memória? Parece que há um problema na implementação.

A minha pergunta principal é, como faço para implementá-lo no caminho certo?

Foi útil?

Solução

Em 2008, forneceu um 98 implementação do padrão de design Singleton C ++ que é lazy-avaliada, garantido-destruição, não-tecnicamente-thread-safe:
Qualquer um pode me fornecer um amostra de Singleton em c ++?

Aqui é um C ++ atualizadas 11 implementação do padrão de design Singleton que é lazy-avaliado, corretamente-destruído, e thread seguro .

class S
{
    public:
        static S& getInstance()
        {
            static S    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        S() {}                    // Constructor? (the {} brackets) are needed here.

        // C++ 03
        // ========
        // Don't forget to declare these two. You want to make sure they
        // are unacceptable otherwise you may accidentally get copies of
        // your singleton appearing.
        S(S const&);              // Don't Implement
        void operator=(S const&); // Don't implement

        // C++ 11
        // =======
        // We can use the better technique of deleting the methods
        // we don't want.
    public:
        S(S const&)               = delete;
        void operator=(S const&)  = delete;

        // Note: Scott Meyers mentions in his Effective Modern
        //       C++ book, that deleted functions should generally
        //       be public as it results in better error messages
        //       due to the compilers behavior to check accessibility
        //       before deleted status
};

Veja este artigo sobre quando usar um singleton: (não muitas vezes)
Singleton: Como deve ser usado

Veja este dois artigos sobre a ordem de inicialização e como lidar:
variáveis ?? estáticos inicialização fim
Finding C ++ problemas de inicialização static

Veja este artigo descrevendo vidas:
Qual é o tempo de vida de uma variável estática em uma C ++ função?

Veja este artigo que discute algumas implicações de threading para singletons:
Singleton exemplo declarado como variável estática do método GetInstance, ele é thread-safe?

Veja este artigo que explica por bloqueio duplo verificado não irá funcionar em C ++:
Quais são todos os comportamentos indefinidos comuns que um programador C ++ deve saber?
Dr Dobbs: C ++ e Os perigos de Duplo Locking -Checked: Parte I

Outras dicas

Sendo um Singleton, você geralmente não quer que ele seja destruído.

Ele vai ficar demolida e desalocada quando o termina programa, que é o comportamento normal, desejada para um singleton. Se você quer ser capaz de limpá-lo explicitamente, é bastante fácil de adicionar um método estático para a classe que lhe permite restaurá-lo para um estado limpo, e tê-lo realocar próxima vez que ele é usado, mas isso é fora do âmbito de um "clássico" singleton.

Você pode evitar a alocação de memória. Existem muitas variantes, todos os que têm problemas em caso de multithreading ambiente.

Eu prefiro este tipo de aplicação (na verdade, não está corretamente disse que eu prefiro, porque eu evitar singletons, tanto quanto possível):

class Singleton
{
private:
   Singleton();

public:
   static Singleton& instance()
   {
      static Singleton INSTANCE;
      return INSTANCE;
   }
};

Não tem nenhuma alocação de memória dinâmica.

de

@Loki Astari resposta é excelente.

No entanto, há vezes com vários objetos estáticos, onde você precisa ser capaz de garantir que o Singleton não será destruído até que todos os seus objetos estáticos que utilizam o Singleton não mais precisa.

Neste caso std::shared_ptr pode ser usado para manter o Singleton vivo para todos os usuários, mesmo quando os destruidores estáticos estão sendo chamados no final do programa:

class Singleton
{
public:
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;

    static std::shared_ptr<Singleton> instance()
    {
        static std::shared_ptr<Singleton> s{new Singleton};
        return s;
    }

private:
    Singleton() {}
};

Outra não-alocação alternativa: criar um singleton, dizer de C classe, como você precisar dele:

singleton<C>()

usando

template <class X>
X& singleton()
{
    static X x;
    return x;
}

Nem este, nem a resposta de Catalin é automaticamente thread-safe na atual C ++, mas estará em C ++ 0x.

A solução na resposta aceita tem uma desvantagem significativa - o destruidor para o singleton é chamado após as folhas de controle da função main(). Pode haver problemas realmente, quando alguns objetos dependentes são alocados dentro main.

Eu conheci este problema, quando se tenta introduzir um Singleton na aplicação Qt. Eu decidi, que todos os meus diálogos de configuração deve ser Singletons, e adotou o padrão acima. Infelizmente, a principal QApplication classe do Qt foi alocada na pilha na função main, e proíbe Qt criação / destruição de diálogos quando nenhum objeto aplicativo está disponível.

É por isso que eu prefiro singletons alocado-heap. I fornecer um explícitas métodos init() e term() para todos os singletons e chamá-los dentro main. Assim eu tenho um controle total sobre a ordem de singletons criação / destruição, e também eu garanto que singletons será criado, não importa se alguém chamado getInstance() ou não.

Se você deseja alocar o objeto na pilha, por que não usar um ponteiro único. Memória também será desalocada já que estamos usando um ponteiro único.

class S
{
    public:
        static S& getInstance()
        {
            if( m_s.get() == 0 )
            {
              m_s.reset( new S() );
            }
            return *m_s;
        }

    private:
        static std::unique_ptr<S> m_s;

        S();
        S(S const&);            // Don't Implement
        void operator=(S const&); // Don't implement
};

std::unique_ptr<S> S::m_s(0);

Eu não encontrei uma implementação CRTP entre as respostas, então aqui está:

template<typename HeirT>
class Singleton
{
public:
    Singleton() = delete;

    Singleton(const Singleton &) = delete;

    Singleton &operator=(const Singleton &) = delete;

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

Para usar apenas herdar sua classe a partir deste, como: class Test : public Singleton<Test>

Aqui é uma implementação fácil.

#include <Windows.h>
#include <iostream>

using namespace std;


class SingletonClass {

public:
    static SingletonClass* getInstance() {

    return (!m_instanceSingleton) ?
        m_instanceSingleton = new SingletonClass : 
        m_instanceSingleton;
    }

private:
    // private constructor and destructor
    SingletonClass() { cout << "SingletonClass instance created!\n"; }
    ~SingletonClass() {}

    // private copy constructor and assignment operator
    SingletonClass(const SingletonClass&);
    SingletonClass& operator=(const SingletonClass&);

    static SingletonClass *m_instanceSingleton;
};

SingletonClass* SingletonClass::m_instanceSingleton = nullptr;



int main(int argc, const char * argv[]) {

    SingletonClass *singleton;
    singleton = singleton->getInstance();
    cout << singleton << endl;

    // Another object gets the reference of the first object!
    SingletonClass *anotherSingleton;
    anotherSingleton = anotherSingleton->getInstance();
    cout << anotherSingleton << endl;

    Sleep(5000);

    return 0;
}

Apenas um objecto criado e esta referência objeto é retornado todos e cada vez afterwords.

SingletonClass instance created!
00915CB8
00915CB8

Aqui 00915CB8 é o local de memória de Singleton objeto, mesmo para a duração do programa, mas (normalmente!) Diferente de cada vez que o programa é executado.

NB. Este não é um segmento seguro one.You tem que garantir a segurança do thread.

Na verdade, é provavelmente atribuídos a partir da pilha, mas sem as fontes não há nenhuma maneira de saber.

A implementação típica (tirado de algum código que eu tenho na já emacs) seria:

Singleton * Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    };
    return instance;
};

... e contar com o programa vai fora do escopo para limpar depois.

Se você trabalha em uma plataforma onde a limpeza deve ser feita manualmente, eu provavelmente adicionar uma rotina de limpeza manual.

Outro problema com a fazê-lo desta maneira é que ele não é thread-safe. Em um ambiente de vários segmentos, dois tópicos poderia passar o "se" antes de qualquer um tem a chance de alocar a nova instância (assim tanto faria). Isso ainda não é muito grande de um negócio se você está confiando ao término do programa para limpar de qualquer maneira.

Alguém std::call_once e std::once_flag mencionado? A maioria das outras abordagens - incluindo o dobro verificado bloqueio -. São quebrados

Um dos principais problemas na implementação padrão Singleton é inicialização segura. A única maneira segura é para guardar a sequência de inicialização com barreiras de sincronização. Mas os próprios barreiras precisam ser iniciadas de forma segura. std::once_flag é o mecanismo para se garantida a inicialização segura.

Além da outra discussão aqui, pode ser interessante notar que você pode ter global de-ness, sem limitar o uso de uma instância. Por exemplo, considere o caso de contagem de referência alguma coisa ...

struct Store{
   std::array<Something, 1024> data;
   size_t get(size_t idx){ /* ... */ }
   void incr_ref(size_t idx){ /* ... */}
   void decr_ref(size_t idx){ /* ... */}
};

template<Store* store_p>
struct ItemRef{
   size_t idx;
   auto get(){ return store_p->get(idx); };
   ItemRef() { store_p->incr_ref(idx); };
   ~ItemRef() { store_p->decr_ref(idx); };
};

Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances

Agora, em algum lugar dentro de uma função (como main) você pode fazer:

auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201); 

Os refs não precisa armazenar uma volta ponteiro para sua respectiva Store porque essa informação é fornecida em tempo de compilação. Você também não precisa se preocupar com a vida do Store porque o compilador requer que é global. Se há de fato apenas uma instância de Store então não há nenhuma sobrecarga nesta abordagem; com mais de uma instância é até o compilador para ser inteligente sobre geração de código. Se necessário, a classe ItemRef pode até ser feita uma friend de Store (você pode ter templated amigos!).

Se a própria Store é uma classe de modelo, em seguida, as coisas ficam mais confusa, mas ainda é possível utilizar este método, talvez através da implementação de uma classe auxiliar com a seguinte assinatura:

template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning 
                       instances of ItemRef<Store_t, store_p>. */ };

O usuário agora pode criar um tipo de StoreWrapper (e instância global) para cada instância Store global, e sempre acessar as lojas através do seu exemplo invólucro (esquecendo-se, assim, sobre os detalhes dos parâmetros de modelo necessários para a utilização Store).

Trata-se de objeto de gerenciamento de tempo de vida. Suponha que você tenha mais do que singletons em seu software. E eles dependem Logger Singleton. Durante destruição aplicação, suponha que um outro objeto singleton usa Logger para registrar seus passos destruição. Você tem que garantir que Logger devem ser limpos passado. Portanto, por favor, confira este artigo: http://www.cs.wustl.edu/~schmidt/PDF/ ObjMan.pdf

O papel que foi relacionado acima descreve a lacuna de bloqueio verificado dupla é que o compilador pode alocar a memória para o objeto e definir um ponteiro para o endereço da memória alocada, antes construtor do objeto foi chamado. É muito fácil em c ++ no entanto, usar allocaters para alocar a memória manualmente e, em seguida, usar uma chamada de construção para inicializar a memória. Usando este appraoch, o bloqueio com verificação dupla funciona muito bem.

#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

Exemplo:

   class CCtrl
    {
    private:
        CCtrl(void);
        virtual ~CCtrl(void);

    public:
        INS(CCtrl);

Simples única classe, este deve ser seu arquivo de classe de cabeçalho

#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H

class SingletonClass
{
    public:
        static SingletonClass* Instance()
        {
           static SingletonClass* instance = new SingletonClass();
           return instance;
        }

        void Relocate(int X, int Y, int Z);

    private:
        SingletonClass();
        ~SingletonClass();
};

#define sSingletonClass SingletonClass::Instance()

#endif

Acesse o seu singleton assim:

sSingletonClass->Relocate(1, 2, 5);

Eu acho que você deve escrever uma função estática que seu objeto estático é excluído. Você deve chamar esta função quando você está prestes a fechar o aplicativo. Isto irá assegurar que você não tem vazamento de memória.

Que tal usar posicionamento novo assim:

class singleton
{
    static singleton *s;
    static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align
    static singleton* getinstance()
    {
        if (s == null)
        {
            s = new(buffer) singleton;
        }
        return s;
    }
};
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top