900 Stimmen

C++ Singleton-Entwurfsmuster

Kürzlich bin ich auf eine Realisierung/Implementierung des Singleton-Designmusters für C++ gestoßen. Sie sah wie folgt aus (ich habe sie aus dem realen Beispiel übernommen):

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

Aus dieser Erklärung kann ich ableiten, dass das Instanzfeld auf dem Heap initiiert wird. Das heißt, es findet eine Speicherzuweisung statt. Was für mich völlig unklar ist, ist, wann genau der Speicher wieder freigegeben werden soll? Oder gibt es einen Fehler und ein Speicherleck? Es scheint, als gäbe es ein Problem mit der Implementierung.

Meine Hauptfrage ist: Wie setze ich es richtig um?

5voto

riderchap Punkte 647

Wenn Sie das Objekt im Heap allozieren wollen, sollten Sie keinen eindeutigen Zeiger verwenden. Der Speicher wird auch freigegeben, da wir einen eindeutigen Zeiger verwenden.

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

4voto

Milind Deore Punkte 2611

C++11 Thread-sichere Implementierung:

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

3voto

T.E.D. Punkte 42630

Es handelt sich wahrscheinlich um eine Zuteilung aus dem Haufen, aber ohne die Quellen kann man das nicht wissen.

Die typische Implementierung (aus einem Code, den ich bereits in emacs habe) wäre:

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

...und sich darauf verlassen, dass das Programm danach aufräumt, wenn es den Rahmen verlässt.

Wenn Sie auf einer Plattform arbeiten, auf der die Bereinigung manuell durchgeführt werden muss, würde ich wahrscheinlich eine manuelle Bereinigungsroutine hinzufügen.

Ein weiteres Problem bei dieser Vorgehensweise ist, dass sie nicht thread-sicher ist. In einer Multithreading-Umgebung könnten zwei Threads das "if" durchlaufen, bevor einer der beiden eine Chance hat, die neue Instanz zuzuweisen (also beide). Dies ist jedoch kein allzu großes Problem, wenn Sie sich darauf verlassen, dass das Programm bei Beendigung sowieso wieder aufgeräumt wird.

2voto

ricab Punkte 2457

Hier ist ein spottbilliges Singleton mit CRTP . Sie stützt sich auf ein kleiner Helfer ein einzelnes Objekt zu einem bestimmten Zeitpunkt (höchstens) durchzusetzen. Um ein einziges Objekt während der Programmausführung zu erzwingen, entfernen Sie das Zurücksetzen (was wir für Tests nützlich finden).

A ConcreteSinleton kann wie folgt implementiert werden:

class ConcreteSingleton : public Singleton<ConcreteSingleton>
{
public:
  ConcreteSingleton(const Singleton<ConcreteSingleton>::PrivatePass&)
      : Singleton<StandardPaths>::Singleton{pass}
  {}

  // ... concrete interface
  int f() const {return 42;}

};

Und dann verwendet mit

ConcreteSingleton::instance().f();

1voto

dan-man Punkte 2671

Zusätzlich zu der anderen Diskussion hier, ist es vielleicht erwähnenswert, dass man Globalität haben kann, ohne die Verwendung auf eine Instanz zu beschränken. Betrachten wir zum Beispiel den Fall der Referenzzählung von etwas...

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

Nun wird irgendwo innerhalb einer Funktion (z. B. main ) können Sie tun:

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

Die Schiedsrichter müssen keinen Zeiger zurück auf ihre jeweiligen Store weil diese Information zur Kompilierzeit bereitgestellt wird. Sie müssen sich auch nicht um die Store Lebensdauer, weil der Compiler verlangt, dass sie global ist. Wenn es tatsächlich nur eine Instanz von Store dann gibt es bei diesem Ansatz keinen Overhead; bei mehr als einer Instanz ist es Sache des Compilers, bei der Codegenerierung clever vorzugehen. Falls nötig, kann der ItemRef Klasse kann sogar zu einer friend von Store (Sie können Freunde mit Schablonen haben!).

Si Store selbst eine Vorlagenklasse ist, werden die Dinge unübersichtlicher, aber es ist immer noch möglich, diese Methode zu verwenden, vielleicht durch die Implementierung einer Hilfsklasse mit der folgenden Signatur:

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

Der Benutzer kann nun eine StoreWrapper Typ (und globale Instanz) für jede globale Store Instanz, und greifen Sie immer über ihre Wrapper-Instanz auf die Speicher zu (und vergessen Sie dabei die blutigen Details der Vorlagenparameter, die für die Verwendung von Store ).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X