2012-06-24 8 views
5

Ich versuche, eine Anwendung zu schreiben, die ihre Erweiterungen während der Laufzeit dynamisch lädt. Ich habe die Boost Preprocessor-Bibliothek verwendet, um eine Präprozessor-Funktion zu schreiben, die bei einer Liste von Namen eine Klasse für jeden Namen deklariert (und alle Unterklassen einer AbstractPlugin-Klasse erzeugt) und dann eine Boost MPL-Sequenz deklariert, die diese Klassen enthält. Dann schrieb ich eine Klasse, die einen Zeiger auf AbstractPlugin ausprobiert, wenn sie auf einen der Typen in dieser MPL-Sequenz umgewandelt werden könnte. Das Problem hier ist, dass meine Präprozessor-Funktion eine vollständige Liste aller Erweiterungen benötigt, die ich erstellen und laden möchte. Gibt es eine Technik, mit der ich jede Erweiterung in einer separaten Datei registrieren kann?Registrieren Sie eine C++ - Klasse, damit später eine Funktion über alle registrierten Klassen iterieren kann

Update:

Ich glaube, meine Erklärung zur Situation zu vage war, so habe ich beschlossen, es präziser zu machen.

Ich möchte eine Sammlung von Erweiterungstypen definieren. Für jeden Erweiterungstyp kann eine beliebige Anzahl von Erweiterungen vorhanden sein. Während der Laufzeit lädt das Programm eine externe Bibliothek, löst die Eingangspunktfunktion auf, ruft sie auf und erhält als Ergebnis einen Zeiger. Dann wird versucht, diesen Zeiger auf alle registrierten Erweiterungstypen zu setzen (unter Verwendung von dynamic_cast, so dass Klassen für Erweiterungstypen alle von einer bestimmten polymorphen Basisklasse erben). Wenn eine Umwandlung in einen Erweiterungstyp erfolgreich ist, wird der Casted Pointer in einem Aufruf des speziellen Handlers für diesen Erweiterungstyp verwendet.

Die Anzahl der Erweiterungstypen ist zur Kompilierungszeit bekannt (während die Anzahl der Erweiterungen natürlich unendlich ist). Mit meinem Ansatz prüft die Loader-Klasse anhand dieser Kenntnisse, ob für jeden Erweiterungstyp ein Handler existiert (falls nicht, kompiliert das Programm nicht). Außerdem erzwingt meine Vorgehensweise keine Klassen für Erweiterungstypen, die etwas über den Loader wissen (so ist es einfach, den Loader zu modifizieren). Aber es wäre bequemer, wenn jeder Erweiterungstyp sich selbst registriert.

+0

Erzeugt eine Kopfzeile eine akzeptable Lösung? – Arpegius

Antwort

0

Wie sich herausstellt, was ich will, ist unmöglich. Der Grund dafür ist "Register" bedeutet in diesem Zusammenhang "setze einen Typ innerhalb der Typfolge" und Typfolgen sind unveränderlich, weil sie selbst Typen sind. Also sollte man diese Typsequenz entweder manuell anlegen, oder wie manche Leute vorgeschlagen haben, die "Registrierung" in Runtime verschieben.

0

Es ist nicht schwierig, ein solches Erweiterungsframework mit dem abstrakten Fabrikmuster zu implementieren.

http://en.wikipedia.org/wiki/Abstract_factory_pattern

Sie können diese abstrakten Werk Funktionen/Objekte in einer globalen Liste, registrieren Sie sich und tun, was Sie Basis auf, es tun möchten.

8

Sie können alle Ihre Klassen in einer Art Sammlung selbst registrieren lassen. Hier ist ein Skelett Ansatz:

Base.hpp:

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

struct Base 
{ 
    virtual ~Base() = default; 

    using create_f = std::unique_ptr<Base>(); 

    static void registrate(std::string const & name, create_f * fp) 
    { 
     registry()[name] = fp; 
    } 

    static std::unique_ptr<Base> instantiate(std::string const & name) 
    { 
     auto it = registry().find(name); 
     return it == registry().end() ? nullptr : (it->second)(); 
    } 

    template <typename D> 
    struct Registrar 
    { 
     explicit Registrar(std::string const & name) 
     { 
      Base::registrate(name, &D::create); 
     } 
     // make non-copyable, etc. 
    }; 

private: 
    static std::unordered_map<std::string, create_f *> & registry(); 
}; 

Base.cpp:

#include "Base.hpp" 

std::unordered_map<std::string, Base::create_f *> & Base::registry() 
{ 
    static std::unordered_map<std::string, Base::create_f *> impl; 
    return impl; 
} 

Nun ist diese zu verwenden, in einem Client:

abgeleitet. hpp:

#include "Base.hpp" 

struct Derived : Base 
{ 
    static std::unique_ptr<Base> create() { return std::make_unique<Derived>(); } 
    // ... 
}; 

derived.cpp:

#include "Derived.hpp" 

namespace 
{ 
    Base::Registrar<Derived> registrar("MyClass"); 
} 

Der Konstruktor der Base::Registrar<Derived> kümmert "MyClass" die Klasse Derived unter dem Namen registrieren.

std::unique_ptr<Base> p = Base::instantiate("MyClass"); 

Der Code könnte/sollte durch Erfassen Wiederholungsanmeldungen, druckt eine Liste der verfügbaren Klassen verbessert werden, usw. Beachten Sie, wie wir alle statische Initialisierung Ordnungsprobleme vermeiden meine die tatsächliche machen: Sie können über Instanzen von Derived dynamisch erstellen Registry-Map-Objekt Ein block-statisches Objekt, das garantiert vor der ersten Verwendung initialisiert und somit erst nach seiner letzten Verwendung zerstört wird.

+0

Ich dachte über so etwas nach, aber ich interessiere mich mehr für eine Kompilierzeit-Approach. Ich bin mir nicht sicher, ob es einen gibt, aber dennoch ist das Land der C++ - Vorlagen voll von Wundern. – grisumbras

+0

Es gibt keine statische. Sie können Klassen nicht selbst registrieren lassen, da jede Implementierungsdatei eine eigene Kompilierungseinheit hat und jede in einer anderen Bibliothek platziert werden kann. Die Stärke dieser Lösung besteht darin, dass beim Laden einer dynamischen Bibliothek die Erweiterungsklasse automatisch verfügbar wird. Es ist ein bekanntes und sehr nützliches Muster. – Arpegius

+0

Dies ist eine sehr nette Implementierung. Das einzige, was ich nicht verstehe, ist das = 0 in der statischen create() - Funktion. Soweit ich weiß, ist das C++ ungültig, da statische Funktionen niemals virtuell sein können. Wie soll dieser Teil funktionieren? – Shenjoku