C++ Singleton-ontwerppatroon

Onlangs ben ik een realisatie/implementatie van het Singleton-ontwerppatroon voor C++ tegengekomen. Het zag er zo uit (ik heb het overgenomen van het echte voorbeeld):

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

Uit deze verklaring kan ik afleiden dat het instantieveld op de heap wordt geïnitieerd. Dat betekent dat er een geheugentoewijzing is. Wat voor mij volkomen onduidelijk is, is wanneer precies het geheugen wordt vrijgegeven? Of is er een bug en geheugenlek? Het lijkt erop dat er een probleem is met de implementatie.

Mijn belangrijkste vraag is, hoe implementeer ik het op de juiste manier?


Antwoord 1, autoriteit 100%

In 2008 heb ik een C++98-implementatie van het Singleton-ontwerppatroon geleverd dat lui is geëvalueerd, gegarandeerde vernietiging en technisch niet veilig is:
Kan iemand mij een voorbeeld van Singleton in c++?

Hier is een bijgewerkte C++11-implementatie van het Singleton-ontwerppatroon dat lui is geëvalueerd, correct is vernietigd en thread- veilig.

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 inaccessible(especially from outside), 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
};

Zie dit artikel over wanneer u een singleton gebruikt: (niet vaak)
Singleton: Hoe moet het worden gebruikt

Zie dit twee artikel over initialisatieorder en hoe het om te gaan:
statische variabelen initialisatieorder
het vinden van C++ statische initialisatieordersproblemen

Zie dit artikel, beschrijving van de levensduur:
Wat is de levensduur van een statische variabele in een C++ -functie?

Zie dit artikel dat sommige inrijgende implicaties op singletons bespreekt:
Singleton-instantie gedeclareerd als statische variabele van de GetInstance-methode, is het draadveilig?

Bekijk dit artikel waarin wordt uitgelegd waarom dubbel gecontroleerde vergrendeling niet werkt op C++:
Wat zijn alle veelvoorkomende ongedefinieerde gedragingen die een C++-programmeur zou moeten kennen?
Dr Dobbs: C++ en The Perils of Double -Gecontroleerd vergrendelen: deel I


Antwoord 2, autoriteit 4%

Als Singleton wil je meestal niet dat het wordt vernietigd.

Het wordt afgebroken en ongedaan gemaakt wanneer het programma wordt beëindigd, wat het normale, gewenste gedrag is voor een singleton. Als je het expliciet wilt kunnen opschonen, is het vrij eenvoudig om een statische methode aan de klasse toe te voegen waarmee je het in een schone staat kunt herstellen en het de volgende keer dat het wordt gebruikt opnieuw kunt toewijzen, maar dat valt buiten het bestek van een “klassieke” single.


Antwoord 3, autoriteit 4%

U kunt geheugentoewijzing vermijden. Er zijn veel varianten, die allemaal problemen hebben in het geval van een multithreading-omgeving.

Ik geef de voorkeur aan dit soort implementatie (eigenlijk wordt niet correct gezegd dat ik de voorkeur geef, omdat ik singletons zoveel mogelijk vermijd):

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

Het heeft geen dynamische geheugentoewijzing.


Antwoord 4, autoriteit 2%

@Loki Astari’s antwoordis uitstekend.

Er zijn echter momenten waarop u bij meerdere statische objecten moet kunnen garanderen dat de singletonniet wordt vernietigd totdat al uw statische objecten die de singletongebruiken nee langer nodig hebben.

In dit geval kan std::shared_ptrworden gebruikt om de singletonin leven te houden voor alle gebruikers, zelfs wanneer de statische destructors aan het einde van het programma worden aangeroepen:

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() {}
};

Antwoord 5

Nog een niet-toewijzend alternatief: maak een singleton, bijvoorbeeld van klasse C, zoals je het nodig hebt:

singleton<C>()

met

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

Noch dit, noch het antwoord van Cătălin is automatisch thread-safe in de huidige C++, maar zal in C++0x zijn.


Antwoord 6

Ik vond geen CRTP-implementatie tussen de antwoorden, dus hier is het:

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

Om te gebruiken, erfen uw klas hierheen, zoals: class Test : public Singleton<Test>


7

Hier is een eenvoudige implementatie.

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

Er is slechts één object gemaakt en dit objectreferentie wordt elke keer geretourneerd en elke keer naWants.

SingletonClass instance created!
00915CB8
00915CB8

Hier 00915CB8 is de geheugenlocatie van Singleton-object, hetzelfde voor de duur van het programma, maar (normaal!) Verschillend telkens wanneer het programma wordt uitgevoerd.

n.b. Dit is geen draad veilig. U moet ervoor zorgen dat draadveiligheid.


8

De oplossing in het geaccepteerde antwoord heeft een aanzienlijk nadeel – de destructor voor de Singleton wordt geroepen nadat de bediening de main()-functie achterblijft. Er kunnen echt problemen zijn, wanneer sommige afhankelijke objecten zijn toegewezen binnenin main.

Ik heb dit probleem ontmoet, toen ik probeerde een singleton in de QT-applicatie te introduceren. Ik besloot, dat al mijn installatie-dialoogvensters singletons moeten zijn en het bovenstaande patroon aangenomen. Helaas is QT’s hoofdklasse QApplicationtoegewezen op stapel in de functie mainen QT-verbiedingen die dialoogvensters maken / vernietigen / vernietigen wanneer er geen toepassingsobject beschikbaar is.

Daarom geef ik de voorkeur aan heap-toegewezen singletons. Ik geef een expliciete init()en term()Methods voor alle singletons en bel ze binnen main. Dus ik heb een volledige controle over de volgorde van Singletons Creation / Destruction, en ik garandeer ook dat singletons zullen worden gemaakt, ongeacht of iemand getInstance()of niet wordt genoemd.


9

Als u het object in de hoop wilt toewijzen, waarom gebruikt u geen unieke aanwijzer. Geheugen zal ook worden gehandeld omdat we een unieke wijzer gebruiken.

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

10

Heeft iemand std::call_onceen std::once_flaggenoemd?
De meeste andere benaderingen – inclusief dubbel gecontroleerde vergrendeling – werken niet.

Een groot probleem bij de implementatie van singleton-patronen is de veilige initialisatie. De enige veilige manier is om de initialisatievolgorde te bewaken met synchronisatiebarrières. Maar die barrières zelf moeten veilig worden geïnitieerd. std::once_flagis het mechanisme om een gegarandeerde veilige initialisatie te krijgen.


Antwoord 11

We hebben dit onderwerp onlangs besproken in mijn EECS-les. Als je de collegeaantekeningen in detail wilt bekijken, ga dan naar http://umich.edu/ ~eecs381/lecture/IdiomsDesPattsCreational.pdf. Deze aantekeningen (en citaten die ik in dit antwoord geef) zijn gemaakt door mijn professor, David Kieras.

Er zijn twee manieren die ik ken om een Singleton-klasse correct te maken.

Eerste manier:

Implementeer het zoals u het in uw voorbeeld hebt. Wat vernietiging betreft: “Singletons houden het meestal vol zolang het programma wordt uitgevoerd; de meeste besturingssystemen zullen geheugen en de meeste andere bronnen herstellen wanneer een programma wordt beëindigd, dus er is een argument om je hier geen zorgen over te maken.”

Het is echter een goede gewoonte om op te ruimen bij het beëindigen van het programma. Daarom kun je dit doen met een extra statische SingletonDestructor-klasse en dat als vriend in je Singleton declareren.

class Singleton {
public:
  static Singleton* get_instance();
  // disable copy/move -- this is a Singleton
  Singleton(const Singleton&) = delete;
  Singleton(Singleton&&) = delete;
  Singleton& operator=(const Singleton&) = delete;
  Singleton& operator=(Singleton&&) = delete;
  friend class Singleton_destroyer;
private:
  Singleton();  // no one else can create one
  ~Singleton(); // prevent accidental deletion
  static Singleton* ptr;
};
// auxiliary static object for destroying the memory of Singleton
class Singleton_destroyer {
public:
  ~Singleton_destroyer { delete Singleton::ptr; }
};
// somewhere in code (Singleton.cpp is probably the best place) 
// create a global static Singleton_destroyer object
Singleton_destoyer the_destroyer;

De Singleton_destroyer wordt gemaakt bij het opstarten van het programma en “wanneer het programma wordt beëindigd, worden alle globale/statische objecten vernietigd door de afsluitcode van de runtime-bibliotheek (ingevoegd door de linker), dus the_destroyer wordt vernietigd; zijn destructor verwijdert de Singleton , die zijn vernietiger uitvoert.”

Tweede weg

Dit wordt de Meyers Singleton genoemd, gemaakt door C++-wizard Scott Meyers. Definieer eenvoudig get_instance() anders. Nu kunt u ook de pointer-lidvariabele verwijderen.

// public member function
static Singleton& Singleton::get_instance()
{
  static Singleton s;
  return s;
}

Dit is netjes omdat de geretourneerde waarde een referentie is en u de syntaxis .kunt gebruiken in plaats van ->om toegang te krijgen tot lidvariabelen.

“Compiler bouwt automatisch code die de eerste keer ‘s’ maakt via de
declaratie, niet daarna, en verwijdert vervolgens het statische object op programma
beëindiging.”

Merk ook op dat je met de Meyers Singleton “in een zeer moeilijke situatie kunt komen als objecten op elkaar vertrouwen op het moment van
beëindiging – wanneer verdwijnt de Singleton ten opzichte van andere objecten? Maar voor eenvoudige toepassingen werkt dit prima.”


Antwoord 12

C++11 Thread veilige implementatie:

#include <iostream>
 #include <thread>
 class Singleton
 {
     private:
         static Singleton * _instance;
         static std::mutex mutex_;
     protected:
         Singleton(const std::string value): value_(value)
         {
         }
         ~Singleton() {}
         std::string value_;
     public:
         /**
          * Singletons should not be cloneable.
          */
         Singleton(Singleton &other) = delete;
         /**
          * Singletons should not be assignable.
          */
         void operator=(const Singleton &) = delete;
         //static Singleton *GetInstance(const std::string& value);
         static Singleton *GetInstance(const std::string& value)
         {
             if (_instance == nullptr)
             {
                 std::lock_guard<std::mutex> lock(mutex_);
                 if (_instance == nullptr)
                 {
                     _instance = new Singleton(value);
                 }
             }
             return _instance;
         }
         std::string value() const{
             return value_;
         }
 };
 /**
  * Static methods should be defined outside the class.
  */
 Singleton* Singleton::_instance = nullptr;
 std::mutex Singleton::mutex_;
 void ThreadFoo(){
     std::this_thread::sleep_for(std::chrono::milliseconds(10));
     Singleton* singleton = Singleton::GetInstance("FOO");
     std::cout << singleton->value() << "\n";
 }
 void ThreadBar(){
     std::this_thread::sleep_for(std::chrono::milliseconds(1000));
     Singleton* singleton = Singleton::GetInstance("BAR");
     std::cout << singleton->value() << "\n";
 }
 int main()
 {
     std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
                 "If you see different values, then 2 singletons were created (booo!!)\n\n" <<
                 "RESULT:\n";
     std::thread t1(ThreadFoo);
     std::thread t2(ThreadBar);
     t1.join();
     t2.join();
     std::cout << "Complete!" << std::endl;
     return 0;
 }

13

Het is inderdaad waarschijnlijk toegewezen aan de hoop, maar zonder de bronnen is er geen manier om te weten.

De typische implementatie (uit een of andere code die ik al in Emacs heb) zou zijn:

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

… en vertrouw op het programma dat uit de ruimte gaat om achteraf op te ruimen.

Als u op een platform werkt waar de opruiming handmatig moet worden gedaan, zou ik waarschijnlijk een handmatige opruimingsroutine toevoegen.

Nog een probleem met het doen van deze manier is dat het niet draadveilig is. In een multithreaded omgeving kunnen twee draden door de “if” komen voordat een kans heeft om het nieuwe exemplaar toe te wijzen (dus beide zouden). Dit is nog steeds niet te groot of u vertrouwt op programma-beëindiging om toch op te ruimen.


14

In aanvulling op de andere discussie hier, is het misschien de moeite waard om op te merken dat u wereldwijd kunt hebben, zonder het gebruik tot één geval te beperken. Overweeg bijvoorbeeld het geval van het tellen van iets …

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

Nu ergens ergens in een functie (zoals main) U kunt doen:

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

De Refs hoeft geen wijzer terug te slaan naar hun respectieve Storeomdat die informatie op Compile-Time wordt geleverd. U hoeft zich ook geen zorgen te maken over de Store‘s levensduur omdat de compiler vereist dat het wereldwijd is. Als er inderdaad slechts één exemplaar van Storeis, is er geen overheadkosten in deze aanpak; Met meer dan één instantie is het aan de compiler om slim te zijn over code generatie. Indien nodig kan de ItemRefClass zelfs een friendvan Store(u kunt geminplateerde vrienden hebben!).

Indien Storezelf is een gemanoneerde klasse, dan wordt het Messier, maar het is nog steeds mogelijk om deze methode te gebruiken, misschien door een helperklasse te implementeren met de volgende handtekening:

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

De gebruiker kan nu een StoreWrapperType (en Global-instantie) maken voor elke Global StoreInstance en altijd toegang tot de winkels via hun Wrapper-instantie (aldus vergeten over de Gory Details van de Malplaatjeparameters die nodig zijn voor het gebruik van Store).


Antwoord 15

Dit gaat over het beheer van de levensduur van objecten. Stel dat u meer dan singletons in uw software heeft. En ze zijn afhankelijk van Logger singleton. Stel dat een ander singleton-object tijdens de vernietiging van toepassingen Logger gebruikt om de vernietigingsstappen te loggen. Je moet ervoor zorgen dat Logger als laatste wordt opgeruimd. Bekijk daarom ook deze krant:
http://www.cs.wustl.edu/~schmidt/PDF/ ObjMan.pdf


Antwoord 16

Mijn implementatie is vergelijkbaar met die van Galik. Het verschil is dat mijn implementatie de gedeelde aanwijzers in staat stelt toegewezen geheugen op te schonen, in plaats van het geheugen vast te houden totdat de toepassing wordt afgesloten en de statische aanwijzers zijn opgeschoond.

#pragma once
#include <memory>
template<typename T>
class Singleton
{
private:
  static std::weak_ptr<T> _singleton;
public:
  static std::shared_ptr<T> singleton()
  {
    std::shared_ptr<T> singleton = _singleton.lock();
    if (!singleton) 
    {
      singleton.reset(new T());
      _singleton = singleton;
    }
    return singleton;
  }
};
template<typename T>
std::weak_ptr<T> Singleton<T>::_singleton;

Antwoord 17

Je code is correct, behalve dat je de instantieaanwijzer niet buiten de klasse hebt gedeclareerd. De inside class-declaraties van statische variabelen worden niet beschouwd als declaraties in C++, maar dit is toegestaan in andere talen zoals C#of Javaenz.

class Singleton
{
   public:
       static Singleton* getInstance( );
   private:
       Singleton( );
       static Singleton* instance;
};
Singleton* Singleton::instance; //we need to declare outside because static variables are global

U moet weten dat Singleton bijvoorbeeld hoeft niet handmatig worden verwijderd door ons . We hebben behoefte aan een enkel object van het gedurende de hele programma, dus aan het einde van de uitvoering van het programma, wordt deze automatisch deallocated.


18

Hier is mijn mening over het doen van goede singletons (en andere niet-triviale statische objecten): https : //github.com/alex4747-pub/proper_singleton

Samenvatting:

  1. Gebruik de statische initialisatielijst om op het juiste moment te instantiëren: na het invoeren van de hoofd en vóór het inschakelen van multi-threading
  2. Voeg kleine verbeteringen toe om het eenheid-testvriendelijk te maken.

19

Ik zou hier een ander voorbeeld van een singleton in C++ willen laten zien. Het is logisch om sjabloonprogrammering te gebruiken. Bovendien is het logisch om uw Singleton-klasse af te leiden van een niet-kopieerbare en geen filmclasses. Hier hoe het eruit ziet in de code:

#include<iostream>
#include<string>
class DoNotCopy
{
protected:
    DoNotCopy(void) = default;
    DoNotCopy(const DoNotCopy&) = delete;
    DoNotCopy& operator=(const DoNotCopy&) = delete;
};
class DoNotMove
{
protected:
    DoNotMove(void) = default;
    DoNotMove(DoNotMove&&) = delete;
    DoNotMove& operator=(DoNotMove&&) = delete;
};
class DoNotCopyMove : public DoNotCopy,
    public DoNotMove
{
protected:
    DoNotCopyMove(void) = default;
};
template<class T>
class Singleton : public DoNotCopyMove
{
public:
    static T& Instance(void)
    {
        static T instance;
        return instance;
    }
protected:
    Singleton(void) = default;
};
class Logger final: public Singleton<Logger>
{
public:
    void log(const std::string& str) { std::cout << str << std::endl; }
};
int main()
{
    Logger::Instance().log("xx");
}

De splitsing in niet-incopyable en not-movable-clasen kunt u uw Singleton specifieker definiëren (soms wilt u uw enkele instantie verplaatsen).


20

het beperken de instantiatie van een klasse naar één object. Dit is handig wanneer er precies één object nodig is om acties in het hele systeem te coördineren

class Singleton {
private:
    int data;
    static Singleton* instance;
    Singleton();
public:
    static Singleton* getInstance();
};
Singleton* Singleton::instance = 0;
Singleton::Singleton()
{
    this->data = 0;
    cout << "constructor called.." << endl;
}
Singleton* Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
        return instance;
    }
}
int main() {
    Singleton *s = s->getInstance();
    Singleton *s1 =s1->getInstance();
    }

Antwoord 21

Het artikel waarnaar hierboven werd gelinkt beschrijft de tekortkoming van dubbel gecontroleerde vergrendeling is dat de compiler het geheugen voor het object kan toewijzen en een pointer kan instellen naar het adres van het toegewezen geheugen, voordat de constructor van het object is aangeroepen. Het is echter vrij eenvoudig in c++ om allocaters te gebruiken om het geheugen handmatig toe te wijzen en vervolgens een construct-aanroep te gebruiken om het geheugen te initialiseren. Met deze benadering werkt de dubbel gecontroleerde vergrendeling prima.


Antwoord 22

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

Voorbeeld:

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

Antwoord 23

Eenvoudige singleton-klasse, dit moet je header-klassebestand zijn

#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

toegang tot uw singleton als volgt:

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

Other episodes