C++ Singleton design pattern

Ostatnio natknąłem się na realizację/implementację Singleton design pattern dla C++. Wyglądało to tak (zaadoptowałem to z przykładu z prawdziwego życia):

// a lot of methods are omitted here
class Singleton
{
   public:
       static Singleton* getInstance( );
       ~Singleton( );
   private:
       Singleton( );
       static Singleton* instance;
};

Z tej deklaracji mogę wywnioskować, że pole instancji jest inicjowane na stercie. Oznacza to, że istnieje alokacja pamięci. Co jest dla mnie zupełnie niejasne, to kiedy dokładnie pamięć zostanie dealokowana? A może jest błąd i wyciek pamięci? Wygląda na to, że jest problem w wdrożenie.

Moje główne pytanie brzmi, jak wdrożyć go we właściwy sposób?

Author: FruitBreak, 2009-06-17

18 answers

W 2008 roku dostarczyłem implementację C++98 Singleton design pattern, która jest leniwa-oceniana, gwarantowana-destrukcja, nie-technicznie-thread-safe:
Czy ktos moze mi podac próbke Singletona w c++?

Oto zaktualizowana implementacja C++11 wzorca projektowego Singleton, który jest leniwie oceniany, poprawnie niszczony i thread-safe .

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
};

Zobacz ten artykuł o tym, kiedy używać Singletona: (nie często)
Singleton: jak powinno być używane

Zobacz ten artykuł o kolejności inicjalizacji i jak sobie z tym poradzić:
kolejność inicjalizacji zmiennych statycznych
znajdowanie problemów z porządkiem inicjalizacji statycznej C++

Zobacz ten artykuł opisujący życie:
jaki jest czas życia zmiennej statycznej w funkcji C++?

Zobacz ten artykuł, który omawia niektóre implikacje wątków do singletonów:
instancja Singletona zadeklarowana jako zmienna statyczna metody GetInstance, czy to bezpieczne?

Zobacz ten artykuł, który wyjaśnia, dlaczego podwójnie sprawdzona blokada nie będzie działać na C++:
jakie są typowe nieokreślone zachowania, o których programista C++ powinien wiedzieć?
Dr Dobbs: C++ and the Perils of Double-Checked Locking: Part I

 867
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-04 12:04:35

Będąc Singletonem, zazwyczaj nie chcesz, aby został zniszczony.

Zostanie zburzony i dealokowany, gdy program zakończy działanie, co jest normalnym, pożądanym zachowaniem dla Singletona. Jeśli chcesz być w stanie jawnie go wyczyścić, dość łatwo jest dodać statyczną metodę do klasy, która pozwala przywrócić go do stanu czystego i ponownie przy następnym użyciu, ale jest to poza zakresem" klasycznego " Singletona.

 37
Author: Reed Copsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-17 16:06:12

Można uniknąć alokacji pamięci. Istnieje wiele wariantów, wszystkie mające problemy w przypadku środowiska wielowątkowego.

Wolę tego typu implementację (właściwie to nie jest poprawnie powiedziane, że wolę, bo unikam singletonów jak najbardziej):

class Singleton
{
private:
   Singleton();

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

Nie posiada dynamicznej alokacji pamięci.

 28
Author: Cătălin Pitiș,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-17 16:10:55

Inna alternatywa niealokująca: Utwórz singleton, powiedzmy klasy C, Jak potrzebujesz:

singleton<C>()

Użycie

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

Ani ta, ani odpowiedź Cătălina nie jest automatycznie bezpieczna dla wątków w bieżącym C++, ale będzie w C++0x.

 10
Author: James Hopkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-17 16:15:51

@odpowiedź Loki Astari jest doskonała.

Są jednak sytuacje, w których istnieje wiele statycznych obiektów, w których musisz być w stanie zagwarantować, że singleton nie zostanie zniszczony, dopóki wszystkie Twoje statyczne obiekty, które używają singleton nie będą już tego potrzebować.

W tym przypadku std::shared_ptr może być użyty do utrzymania singleton przy życiu dla wszystkich użytkowników, nawet gdy na końcu programu wywoływane są statyczne destruktory:

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() {}
};
 9
Author: Galik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:47

Jeśli chcesz przydzielić obiekt w stercie, dlaczego nie użyć unikalnego wskaźnika. Pamięć będzie również dealokowana, ponieważ używamy unikalnego wskaźnika.

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);
 5
Author: riderchap,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-28 13:20:35

Rozwiązanie w zaakceptowanej odpowiedzi ma znaczącą wadę - destruktor dla singletonu jest wywoływany po opuszczeniu funkcji main(). Mogą wystąpić problemy, gdy wewnątrz main zostaną przydzielone pewne obiekty zależne.

Napotkałem ten problem, próbując wprowadzić Singleton w aplikacji Qt. Zdecydowałem, że wszystkie moje okna konfiguracyjne muszą być Singletonami i zastosowałem powyższy wzór. Niestety, główna klasa Qt QApplication została przydzielona na stos w main funkcji, a Qt zabrania tworzenia / niszczenia okien dialogowych, gdy żaden obiekt aplikacji nie jest dostępny.

Dlatego wolę singletony przydzielane na stosie. Podaję jawne metody init() i term() dla wszystkich singletonów i wywołuję je wewnątrz main. Mam wiÄ ™ c peĹ 'nÄ ... kontrolÄ ™ nad kolejnoĹ" ciÄ ... tworzenia/niszczenia singletonăłw, a takĹĽe gwarantujÄ ™ ĹĽe singletony bÄ ™ dÄ ... tworzone, bez wzglÄ ™ du na to czy ktoĹ "wywoĹ' a getInstance() czy nie.

 5
Author: SadSido,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-01 23:17:44

Oto prosta implementacja.

#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;
}

Utworzony jest tylko jeden obiekt i to odniesienie do obiektu jest zwracane za każdym razem po słowach.

SingletonClass instance created!
00915CB8
00915CB8

Tutaj 00915CB8 to miejsce pamięci Singletona, takie samo na czas trwania programu, ale (normalnie!) różne za każdym razem, gdy program jest uruchamiany.

N. B. nie jest to wątek Bezpieczny.Musisz zapewnić bezpieczeństwo nici.
 4
Author: Tunvir Rahman Tusher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-17 20:17:04

Jest rzeczywiście prawdopodobnie przypisany ze sterty, ale bez źródeł nie ma możliwości poznania.

Typowa implementacja (zaczerpnięta z kodu, który mam już w Emacsie) to:

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

...i polegaj na tym, że program będzie poza zasięgiem, aby posprzątać później.

Jeśli pracujesz na platformie, na której Sprzątanie musi być wykonywane ręcznie, prawdopodobnie dodałbym ręczną procedurę sprzątania.

Kolejny problem z robieniem tego w ten sposób jest to, że nie jest bezpieczny dla wątków. W środowisko wielowątkowe, dwa wątki mogą przejść przez "Jeśli", zanim któryś z nich będzie miał szansę przydzielić nową instancję (więc oba będą). To nadal nie jest zbyt duża sprawa, jeśli i tak polegasz na zakończeniu programu, aby posprzątać.

 2
Author: T.E.D.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-17 16:14:23

Chodzi o zarządzanie czasem życia obiektu. Załóżmy, że masz więcej niż singletony w swoim oprogramowaniu. I zależą od Loggera Singletona. Załóżmy, że podczas niszczenia aplikacji inny obiekt singleton używa Loggera do zapisywania kroków niszczenia. Musisz zagwarantować, że Logger zostanie wyczyszczony jako ostatni. Dlatego proszę również zapoznać się z tym artykułem: http://www.cs.wustl.edu/ ~ schmidt / PDF / ObjMan. pdf

 1
Author: baris.aydinoz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-01 08:42:05

Nie znalazłem implementacji CRTP wśród odpowiedzi, więc oto ona:

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;
    }
};

Aby użyć po prostu dziedziczyć swoją klasę z tego, jak: class Test : public Singleton<Test>

 1
Author: Yuriy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-14 12:15:03

Czy ktoś wspomniał std::call_once i std::once_flag? Większość innych podejść-w tym blokada podwójnie sprawdzona - jest zepsuta.

Jednym z głównych problemów implementacji wzorców Singletona jest Bezpieczna inicjalizacja. Jedynym bezpiecznym sposobem jest ochrona sekwencji inicjalizacji za pomocą barier synchronizujących. Ale te bariery muszą być bezpiecznie zainicjowane. std::once_flag jest mechanizmem gwarantującym bezpieczną inicjalizację.

 1
Author: Red.Wave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-10 13:14:32
#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

Przykład:

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

    public:
        INS(CCtrl);
 0
Author: Gank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 19:05:14

Oprócz innych dyskusji tutaj, warto zauważyć, że możesz mieć globalne-ness, bez ograniczania użycia do jednej instancji. Na przykład, rozważmy przypadek odniesienia liczenia czegoś...

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

Teraz gdzieś wewnątrz funkcji (np. main) możesz zrobić:

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

Refy nie muszą przechowywać WSKAŹNIKA z powrotem do swoich Store, ponieważ informacje te są dostarczane w czasie kompilacji. Nie musisz się też martwić o życie Store, ponieważ kompilator wymaga, aby był globalny. Jeśli rzeczywiście istnieje tylko jedna instancja Store , to nie ma narzutu w tym podejściu; w przypadku więcej niż jednej instancji to kompilator powinien być mądry w kwestii generowania kodu. Jeśli to konieczne, klasa ItemRef może być nawet friend z Store (możesz mieć szablonowych przyjaciół!).

Jeśli Store sama w sobie jest klasą szablonową, wtedy rzeczy stają się messier, ale nadal można użyć tej metody, być może implementując klasę pomocniczą o następującej podpis:

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>. */ };

Użytkownik może teraz utworzyć typ StoreWrapper (i instancję globalną) dla każdej instancji globalnej Store i zawsze uzyskać dostęp do magazynów poprzez ich instancję wrappera (zapominając w ten sposób o krwawych szczegółach parametrów szablonu potrzebnych do użycia Store).

 0
Author: dan-man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-09 11:24:30

Prosta klasa singleton, to musi być plik klasy nagłówka

#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

Dostęp do singletonu w ten sposób:

sSingletonClass->Relocate(1, 2, 5);
 0
Author: Ali Khazaee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-11 07:23:23

Myślę, że powinieneś napisać statyczną funkcję, w której twój obiekt statyczny zostanie usunięty. Należy wywołać tę funkcję, gdy masz zamiar zamknąć aplikację. Zapewni to, że nie masz wycieku pamięci.

 -1
Author: Yogi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-18 11:38:22

Artykuł, do którego nawiązano powyżej, opisuje wadę blokowania double checked polegającą na tym, że kompilator może przydzielić pamięć dla obiektu i ustawić wskaźnik na adres przydzielonej pamięci, zanim konstruktor obiektu zostanie wywołany. W c++ jest jednak dość łatwo używać alokatorów do ręcznego przydzielania pamięci, a następnie używać wywołania construct do inicjalizacji pamięci. Używając tego appracha, podwójnie sprawdzony zamek działa dobrze.

 -1
Author: sdfsdaf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-17 22:06:18

A co powiesz na umieszczenie nowego w ten sposób:

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;
    }
};
 -5
Author: rahul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-24 15:59:00