Klassen in dynamischer Bibliothek registrieren.
-
Folgendes Szenario:
Ich habe eine dynamische Biblitohek (DLL in Windows, .so in Linux...), die fast ausschließlich Klassen enthält, die von einer Basisklasse "Basis" abgeleitet werden.
Von diesen Klassen soll es jeweils nur eine Instanz geben, also quasi Singletons. Ferner soll es in der DLL eine Funktion GetClasses() geben, die eine Nullterminierte Liste aus Pointern auf diese Klasseninstanzen/Singletoninstanzen zurückgibt.Die Anwendung lädt dann die DLL, ruft GetClasses() auf, und arbeitet dann mit dem Basis-Interface der einzelnen Instanzen.
Was ich jetzt erreichen möchte ist folgendes: die DLL wird ständig erweitert, und ich möchte es den nachfolgenden Entwicklern leicht machen, ihre Ableitungen in die Instanzenliste zu packen. Bisher war mein Ansatz folgender:
//Basis class Basis { public: virtual int Foo() = 0; virtual int Bar() = 0; virtual ~Basis() = 0; }; // manager.h: Manager in der DLL class BasisManager { public: static Basis** GetInstanceList(); static bool Register(Basis* pb); //auf den Sinn von bool komm ich noch static std::vector<Basis*> pbVec; }; //manager.cpp: std::vector<Basis*> BasisManager::pbVec(1, (Basis*)NULL); //inialisiert mit einem nullpointer Basis** BasisManager::GetInstanceList() {return &pbVec[0];} bool BasisManager::Register(Basis* pb) { pbVec.back() = pb; //nullpointer auf neuen Pointer aendern pbVec.push_back(NULL); //neuen Nullpointer anhaengen return true; } //dllmain.cpp // die Makros uebernehmen die extern-Deklarationen usw. und kapseln den OS-abhaengigen kram. BEGIN_EXTERN_C DECLARE_FUNCTION(Basis**, GetClasses)(); END_EXTERN_C DEFINE_FUNCTION(Basis**, GetClasses)() { return BasisManager::GetInstanceList(); }soweit, so gut. Jetzt müsste ich für jede Basis-Ableitung in der DLL irgendwo das Register im Manager aufrufen, so dass es direkt beim Laden der DLL ausgeführt wird. Außerdem sind die Singleton-Angelegenheiten überall einzubauen usw. Mein Ansatz war mit einem Makro:
#define DEFINE_BASISCLASS(classname) \ \ class classname : public Basis \ { \ public: \ static classname* GetPInstance() \ { \ static classname theInstance; \ return &theInstance; \ } \ \ const static bool b; \ \ \ int Foo(); \ int Bar(); \ }; \ const bool classname::b \ = BasisManager::Register(classname::GetPInstance())Das Registrieren soll also durch die Initialisierung der statischen Variablen in der jeweiligen Klasse geschehen. Jetzt braucht der Entwickler für die neuen Klassen nurnoch folgendes machen:
//newclass.cpp DEFINE_BASISCLASS(MyNewBasisDerivate); MyNewBasisDerivate::Foo() { return 25; } MyNewBasisDerivate::Bar() { return 42; }und fertig. Die Klasseninstanz wird automatisch in der DLL erzeugt, registriert und mit dem ersten Aufruf von GetClasses() an die Anwendung vermittelt.
Der Haken: Makros sind bei uns im Code strikt verboten. Kennt jemand ne Alternative?
ich hab folgendes versucht:
template <class T> class RegisteredBasis : public Basis { RegisteredBasis() { } static Basis* GetPInstance() { static T theInstance; return &theInstance; } const static bool b; }; template <class T> const bool RegisteredBasis<T>::b = BasisManager::Register(RegisteredBasis<T>::GetPInstance());Allerdings scheint bei Templates das statische Member nicht direkt initialisiert zu werden, zumindest wurde unter MSVC nichts reistriert auf die Art.
-
In deinem statischen Member hast du momentan noch einen Syntaxfehler (zwei = statt einem).
Hier greift die übliche Template-Regel (wie z.B. bei Funktionen): Was nicht verwendet wird, wird nicht instanziiert. In dem Falle dürfte ein
b;in einer sicher aufgerufenen (und damit instanziierten) Methode reichen...
Hab grad eben nachgeschaut:
C++ Standard, §14.7.1 schrieb:
The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, static data members and member templates;
-
Nexus schrieb:
In deinem statischen Member hast du momentan noch einen Syntaxfehler (zwei = statt einem).
Sorry, hab ich geändert.
Hier greift die übliche Template-Regel (wie z.B. bei Funktionen): Was nicht verwendet wird, wird nicht instanziiert. In dem Falle dürfte ein
b;in einer sicher aufgerufenen (und damit instanziierten) Methode reichen...
Hab grad eben nachgeschaut:
C++ Standard, §14.7.1 schrieb:
The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, static data members and member templates;
Das hab ich mir schon gedacht dass das das Problem ist. Ich habs versucht, indem ich in den Ctor des Templates ein
(void*)b;geschrieben hab. Der Gedanke war, dass durch die Erzeugung des Ctors der abgeleiteten Klassen ja auch der Ctor des Templates instantiiert werden muss und darüber dann der Zugriff auf b für die Instantiierung von b sorgt.
Scheint aber Fehlanzeige zu sein. Eventuell weils der Ctor der abgeleiteten Klasse ein automatisch generierter Default-Ctor ist und der nur dann generiert wird, wenn er tatsächlich aufgerufen wird?Ich hab zwar einen Aufurf des Ctors im Code, aber nur in der GetPInstance()-Funktion, die wiederum eine Template-Funktion ist und nur instantiiert wird, wenn b initialisiert wird - was nur der Fall ist, wenn der default-Ctor der template-Klasse aufgerufen wird, der nur vom automatisch generierten Ctor der abgeleiteten Klasse aufgerufen wird. Gna. Tefeulskreis

Hat jemand n Tip?
-
Nächster Ansatz:
Ich definiere in der Template-Klasse eine der pur virtuellen Funktionen aus der Basisklasse. In dieser Definition erwähne ich b und zwinge den Compiler damit, b zu instantiieren -> Register wird aufgerufen. Auf MSVC funktionierts, vermutlich weil die vtable für die abgeleitete Klasse aufgebaut wird und daher auch erstmal die vtable für die Templateklasse - und um einen Pointer auf die Definition der Funktion zu bekommen muss die instantiiert werden.
Allerdings steht im Standard nichts von vtables, so dass leider die Bedingungen für die Instantiierung von virtuellen Memberfunktionen recht lasch sind:C++ Standard, §14.7.1,9 schrieb:
It is unspecified whether or not an implementation implicitly instanciates a virtual member function of a class template if the virtual member function would not otherwise be instanciated.
Heißt, es kann funktionieren, muss aber nicht. Hmpf

-
pumuckl schrieb:
Ich habs versucht, indem ich in den Ctor des Templates ein
(void*)b;geschrieben hab. Der Gedanke war, dass durch die Erzeugung des Ctors der abgeleiteten Klassen ja auch der Ctor des Templates instantiiert werden muss und darüber dann der Zugriff auf b für die Instantiierung von b sorgt.
Scheint aber Fehlanzeige zu sein. Eventuell weils der Ctor der abgeleiteten Klasse ein automatisch generierter Default-Ctor ist und der nur dann generiert wird, wenn er tatsächlich aufgerufen wird?Und irgendein Konstruktor der abgeleiteten Klasse muss ja aufgerufen werden, wenn das Objekt instanziiert werden soll (was wahrscheinlich das Ziel ist, wenn du in der Basisklasse virtuelle Methoden hast). Und die einzige Möglichkeit, das Basisobjekt zu konstruieren, wäre dann dessen Defaultkonstruktor aufzurufen. Das sollte eigentlich schon klappen.
Ein Minimalbeispiel:
#include <iostream> #include <typeinfo> template <typename T> bool Initializer() { std::cout << "Init: " << typeid(T).name() << std::endl; return true; } template <typename T> struct MyClass { static const bool b; MyClass() { // Das führt zur Definition von b static_cast<void>(b); } }; template <typename T> const bool MyClass<T>::b = Initializer<T>(); template <typename T> struct Derived : public MyClass<T> { }; int main() { Derived<int> i; Derived<double> d; }Ausgabe (Visual Studio 2008 Professional):
Init: int Init: double
-
Also, dynamisches Laden von Klassen? Der folgende Code z.B. in Hero.cpp (und als .so compiliert) registriert bei der ClassFactory unter dem Namen "Hero" den Konstruktor und Destruktor der Klasse Hero. Wenn man die Anzahl der Instanzen kontrollieren moechte, dann muss die Classfactory eben eine Methode anbieten, die Strings auf Klassen mapped und das Objekt nur dann konstruiert, wenn noch keins da ist. Die Proxyklasse kann sicher in ein Template gekapselt werden, aber fuer die automatische Registrierung muss dieses trotzdem noch konstruiert werden. Wie es einfacher gehen soll, weiss ich nicht. Am Lua-Code nicht stoeren.
namespace { // create and destroy functions for class factory IObject* create() { return new Hero(); } void destroy(IObject* ptr) { delete ptr; } class Proxy { public: Proxy() { ClassFactory::get()->reg("Hero",&create,&destroy); LuaConsole::get()->reg("Hero",hero_lib); } }; // create proxy class while lib opening // constructor does the stuff for initialisation Proxy p; }Anregungen habe ich mir damals hier geholt: http://www.linuxjournal.com/article/3687 . Um fuer Betriebsystem spezifische Laden von Bibliotheken zu kapseln, habe ich mir damals auch eine Klasse LibLoader geschrieben. Getestet wurde aber alles nur unter Linux.
-
Nexus schrieb:
...
Das funktioniert, solang du einen der generierten Teile irgendwo aufrufst und damit den Compiler zwingst, auch den Rest zu generieren. Bei mir wird aber nichts explizit aufgerufen außer der GetBasis()-Funktion in der DLL. Im Makro war es noch so, dass die Initialisierung des statischen bool für jede Klasse explizit aufgerufen wurde (Aufruf durchs Makro generiert). Mit dem Template gibts aber wie oben gesagt keinen expliziten Aufruf mehr, da auch der Default-Ctor der abgeleiteten Klasse nur generiert wird, wenn er aufgerufen wird - was aber nur der Fall ist, wenn das GetPInstance() des Templates instantiiert wird. Besagter Teufelskreis eben.
knivil schrieb:
...
Ja, wie du esgesagt hast gehts mehr oder weniger um dynamisches Laden von Klassen. In deinem Beispiel machst du aber genau das manuell, was ich automatisch/generiert machen möchte: das Registrieren.
In dem Projekt an dem ich arbeite soll es ca. 30 derartige DLLs geben mit insgesamt geschätzten 1500 derartigen Klassen. Wenn ich in jeder DLL eine große Funktion habe wo mal eben ein paar hundert Klassen registriert werden wirds unübersichtlich, deshalb der Wunsch, das durch solche Tricks wie das Makro oder Templates zu lösen.
-
Leider sind Templates in C++ nicht so maechtig wie Lisp/Scheme Makros. Wenn du auf Makros verzichten musst, dann bietet sich noch ein Vor-/Nachbearbeitungsschritt an, bei dem ein kleines Tool den entsprechenden Quelltext generiert und der im Buildprozess beruecksichtigt wird. Aber vielleicht kannst du die strikte Doktrin "keine Makros" bei deinem Chef etwas aufweichen, hier gibt es gute Gruende, sie zu nutzen.
-
Leider hast du recht. Vielleicht finde ich noch ein etwas verdaulicheres Makro, was den Code-QS-Leuten nicht so sauer aufstößt

Noch einen zusätzlichen Generierungsprozess einzubauen klingt schön, ist aber dann doch irgendwo Overkill