2016-04-13 8 views
3

ich eine Klasse mit der folgenden statischen Methode geschrieben haben:C++ Funktion mit Nebeneffekt bei Dateigültigkeitsbereich verwendet, greift Singleton

MyMap& Manager::GetMap(void) 
{ 
    static MyMap* factories = new MyMap(); 

    return (*factories); 
} 

Wo „MyMap“ ist ein typedef für:

unordered_map<string, function<Base* (Dependency& d)>> 

Es gibt auch eine Vielzahl von Typen, die von Base abgeleitet sind, z

class Derived1 : public Base 
{ 
public: 
    Derived1(Dependency& d); 
}; 

Betrachten Sie die folgende Verwendung.

definiere ich die folgenden in einer Implementierungsdatei für Derived1:

#include "Derived1.h" 
#include "Manager.h" 

int RegisterDerived1(void) 
{ 
    Manager::GetMap()["Test"] = [](Dependency& d){ return new Derived1(d); }; 

    return 0; 
} 

int Reg = RegisterDerived1(); 

Sie können keine Funktionen im Dateigültigkeitsbereich aufrufen, aber Sie können den Rückgabewert einer Funktion zu einer globalen Variablen zugewiesen werden, auch wenn diese Funktion hat Nebenwirkungen. Zu dem Zeitpunkt, an dem "Manager" verwendet wird, wird daher "MyMap" String/Funktionspaare für verschiedene abgeleitete Arten von "Base" (bis jetzt) ​​enthalten. Die Absicht ist, dass neue abgeleitete Typen von "Base" sich mit "Manager" registrieren, in der Lage, Instanzen dieses Typs zu konstruieren und basierend auf einem Namen den Typ auszuwählen.

Ich frage mich, ob dies ein sicheres Verhalten darstellt und/oder ob es alternative Implementierungen gibt, um den gewünschten Effekt zu erzielen?

Ich wurde auf diesen Artikel aufmerksam gemacht, der ein generisches Registrierungsobjekt vorschlägt, das das obige Paar in seinem Konstruktor übernimmt und die Registrierung durchführt, von der dann eine statische Instanz für jede zu registrierende Klasse definiert wird.

http://accu.org/index.php/journals/597

+0

Es scheint, dass Sie im Grunde „Verzögerte Initialisierung“ fragen und die anderen Code bezogene Dinge sind nicht viel relevant. Haben Sie das Gefühl, beantwortet dieser Beitrag Ihr Q: [Lazy Initialisierung mit Singleton-Muster] (http://stackoverflow.com/q/21252296/514235). Wenn ja, kann es als Duplikat geschlossen werden. – iammilind

+0

@iammilind Ich bin klar auf das Verhalten der Singleton-Implementierung, das ist, was ich denke, dass diese Frage ist. Ich habe nur die anderen Bits für den Kontext eingefügt. Was mir weniger klar ist, sind die Auswirkungen der Verwendung der obigen freien Funktion im Dateibereich. Es fühlt sich an wie eine schlechte Sache, da ich eine Einschränkung umgehen muss. –

Antwort

1

Das Prinzip ist in Ordnung.

Ein paar Dinge, die Sie betrachten wünschen können:

  1. Rückkehr rohe Zeiger sind eine schlechte Idee - stattdessen verwenden unique_ptr.

  2. Wollten Sie wirklich, dass die Referenz & nicht konstant ist?

  3. Verbergen Sie die interne Implementierung. Benutzer müssen nicht wissen, dass es sich um eine ungeordnete Map handelt.

Eine leicht modifizierte Version mit Inline-Kommentare für Sie zu prüfen:

#include <functional> 
#include <unordered_map> 
#include <memory> 
#include <string> 

struct Base 
{ 
    virtual ~Base() = default; 
}; 

struct Dependency 
{ 

}; 

struct Manager 
{ 
    // I notice that Depdendency& is not const. Was that what you wanted? 
    using factory_function = std::function<std::unique_ptr<Base> (Dependency& d)>; 

    // public registration function hides internal implementation of map 
    static bool register_function(const std::string ident, factory_function f) 
    { 
    return GetMap().emplace(std::move(ident), std::move(f)).second; 
    } 

    // public create function hides internal implementation of map 
    // returns a unique_ptr - much better! 
    static std::unique_ptr<Base> create(const std::string& ident, Dependency& d) 
    { 
    // this will throw an exception if the factory does not exist. 
    // another implementation could substitute a known version of Base, 
    // for example. But now it's under your control and the user does 
    // not have to think about it. 
    return GetMap().at(ident)(d); 
    } 

    private: 

    using MyMap = std::unordered_map<std::string, factory_function>; 

    // private map implementation. In future we may want to add a mutex 
    // (in case the map can be dynamically updated?) 
    // so let's encapsulate 
    static MyMap& GetMap() 
    { 
    // no need for new here. Static variables are cleanly destructed at 
    // the end of the program, and initialised the first time the code 
    // flows over them. 
    static MyMap _map; 
    return _map; 
    } 
}; 

struct Derived1 : Base 
{ 
    Derived1(Dependency&) {} 
}; 

// now we don't need to care about Manager's implementation. 
// this is better - we are decoupled. 
bool derived1_registered = Manager::register_function("Derived1", 
                [](Dependency& d) 
                { 
                 return std::make_unique<Derived1>(d); 
                }); 

int main() 
{ 
    Dependency d; 
    auto p = Manager::create("Derived1", d); 

    return 0; 
} 
+0

Ja, es gibt nicht-konstante Methoden, an denen ich an dieser Dependency-Klasse interessiert bin. Die Punkte 1 und 3 sind jedoch gut gewählt. Irgendwelche Gedanken über die Verwendung der freien Funktion auf diese Weise? –

+1

die freie Funktion ist absolut in Ordnung. Sie könnten es sogar templatisieren, wenn Sie wollten, also müssen Sie es nur einmal schreiben. –