ifdefs über virtuelle subklassen auslagern?
-
Hallo,
Ich würde gerne von euch eure Meinung bzw. verbesserungsvorschläge hören.
Ich habe vor alle #ifdefs in virtuelle Subklassen auszulagern.
Ich habe eine sequentielle und parallele Implementierung die gleiche signaturen der funktionen/methoden bekommen aber unterschiedliche implementierungen.somit möchte ich 2 klassen (sequentialClass parallelClass) erstellen die beide von einer anderen superklasse erben in der die implementierung ist die beide varianten benutzen (MyClass z.B.)
Eine Factory-Classe hat dann 1 #ifdef drin wo je nach compileroption dann entweder ein objekt mit sequentiellen oder parallelen methoden "erstellt wird".
Fragen:
-
Ist diese vorgehensweise sinnvoll? Ich will alle ifdefs speziell auslagern und will auch templatebasierte subklassen sequentialClass und parallelClass haben.
-
Nur wie könnte ich dann bzw. wo könnte ich code von beiden implementierungen setzen? Käme der dann in MyClass oder muss ich dann im restlichen code einfach separat über ein OBjekt von MyClass auf die Methoden zugreifen?
Danke für eure Ideen/Hilfe
-
-
Halte ich für sehr sinnvoll. Das macht es dir sogar noch recht einfach möglich noch weitere Implementierungen zu probieren, ohne den Grossteil des Code anzupassen.
Zu 2. Du schreibst ja selbst, dass du da Vererbung haben möchtest, also befindet sich der Code dann in den geerbten Klassen. So, wie ich das verstehe soll MyClass ja nur die Schnittstelle anbieten, also die Funktionsnamen und dann leitest du davon ab und implementierst die Funktionen. Dann könntest du sogar zur Laufzeit noch die Auswahl machen. Wenn das nicht gewünscht ist, wären templates wahrscheinlich die bessere Wahl, anstatt Polymorphie.
-
Danke für die Antwort.
Ich versuche gerade ein Minimalbeispiel aufzusetzen.
Wie sieht es mit der Performance aus? Der Templatebasierte Ansatz dürfte schneller sein - die frage ist nur um wieviel? Wenn ich jetzt sehr sehr oft eine virtuelle methode aufrufe - würde das nicht teuer werden? Abgesehen davon dass sie vielleicht im cache oder in nem register zu liegen kommt...Zu meinem Punkt 2.)
Ich dachte ich habe die sachen die sowohl sequentiell als auch parallel identisch sind z.B. in einer Klasse Foo. Innerhalb dierser klasse erstelle ich mir dann ein objekt MyClass. In MyClass sind nur die virtuellen methoden enthalten und je nach flag wird dann die subklassenimplementierung entweder SequentialClass oder ParallelClass instantiiert damit ich zugriff auf die #ifdef spezifischen sachen habe.Ich bin mir hier nicht sicher wo ich das zeug hinpacken könnte was beide dinge verwenden....
Zusätzlich auch die frage ob der templatebasierte ansatz wirklich sehr sehr viel schneller ist...
-
Solche Optimierungsfragen solltest du dir nicht stellen, weil die nur sehr spezifisch beantwortet werden können und es ist besser ein sauberes und erweiterbares, verständliches Design zu haben und sich dann über Detail Fragen Gedanken zu machen, wenn du merkst, dass es nötig ist.
Prinzipiell willst du die Auswahl zur Kompilierungszeit, also templates und keine virtuellen Funktionen. Wozu auch, wenn du das nicht benötigst?
class sequential { void do_this_stuff (int p /*...*/ ); }; class parallel { void do_this_stuff (int p /*...*/ ); }; template<typename technique> class manager { void general_stuff () { // mach allgemeines } void special () { technique t; // mach ein Objekt von dem im moment gar nichts weisst, ausser, dass es t.do_this_stuff ( 2 ); // diese Funktion hier hat, also kannst du sie auch aufrufen } }; int main { manager<parallel> m1; // hier kannst du z.B die Auswahl machen zur Kompilierungszeit machen und gibst an welchen Typ du wirklich haben willst manager<sequential> m2; m1.general_stuff (); m1.special (); m2.general_stuff (); m2.special (); }
Das ist eigentlich eine Art Policy Based Design.
Sehr schön erweiter- und kombinierbar.
-
super - danke drakon - das hat mir sehr geholfen. Ich verstehe auch den Template-Ansatz und versuche jetzt das auf virtuelle Funktionen zu übertragen.
Eine Zusatzfrage hätte ich noch. Da ich verschiedene Datentypen bekomme müsste
die sequentielle und parallele klasse templatebasiert sein. Macht es Sinn oder ist es gutes/schlechtes Design den virtuellen Ansatz (also nicht den den du oben beschrieben hast mit code) mit Templatebasierten Klassen (in dem fall sequential und parallel) zu mischen?Danke!
Der Ansatz mit virtuellen Methoden stimmt noch net so ganz :)....ich arbeite dran...falls jemand nen stubser geben will....gerne.
#include <string> #include <stdlib.h> #include <sstream> #include <iostream> class manager { public: virtual ~manager(){}; void general_stuff() { std::cout << "general..." << std::endl; } virtual void special (int p) = 0; }; class sequential : manager { public: void do_this_stuff(int p) { std::cout << "sequential: " << p << std::endl; } }; class parallel : manager { public: void do_this_stuff(int p) { std::cout << "parallel: " << p << std::endl; } }; int main(int argc, int** argv) { #ifdef ENV_MPI manager m; // hier kannst du z.B die Auswahl machen zur Kompilierungszeit machen und gibst an welchen Typ du wirklich haben willst #else manager m; #endif m.general_stuff (); m.special(2); }
-
Ich verstehe nicht, warum du unbedingt virtuelle Funktionen haben willst? - Das ist völlig unnötig, wenn die Auswahl zur Kompilierungszeit statt findet. Du brauchst da schlichtweg keine Laufzeitauswahl, also solltest du sie auch nicht benutzen..
Aber gehen tut es prinzipiell schon auch, ich denke du willst so was hier:
int main(int argc, int** argv) { #ifdef ENV_MPI manager* m = new sequential; #else manager* m = new parallel; #endif m.general_stuff (); m.special(2); delete manager; }
Aber wie gesagt da solche Laufzeit Techniken zu benutzen, wenn du sowieso lediglich zur Kompilierungszeit eine Auswahl machst halte ich für schlecht und sogar eher Fehleranfällig. (Wer hält dich ab nicht zur Laufzeit eine andere Zuweisung zu machen, wenn das nicht sein soll?)
-
danke erneut. Ich denke darüber nach und mir scheint es auch plausibler über templates zu gehen da ich ja bereits zur compilezeit weiß was los ist. Ich denke ich werde mich mit dem template-ansatz begnügnen...
ich versuche noch gründe zu finden die für den template-ansatz sprechen...
Wer hält dich ab nicht zur Laufzeit eine andere Zuweisung zu machen, wenn das nicht sein soll?
niemand - das ist wohl das was du meintest oder?
Neben performance suche ich noch weitere vorteile des template-ansatzes...
danke für die hilfe
-
Du solltest das anderst anschauen.
Du hast 2 Möglichkeiten.
1. Virtuelle Funktionen usw. und
2. Templates.Grob gesagt:
1. Spricht für Laufzeitverhalten
2. Wird benutzt, wenn Polymorphie nicht zur Laufzeit, sondern während der Kompilierung statt findet.Also fällt die intuitive Wahl in deinem Problem auf die 2, also templates.
Jetzt solltest du Punkte suchen, warum Laufzeit Polymorphie gebraucht werden sollte.Du hast einen Hang zur Laufzeitpolymorphie. Warum weiss ich nicht, aber ich vermute du kommst von einer anderen Sprache, wo du das so lösen würdest. Aber du darfst da jetzt nicht einfach mal das, was du bereits kennst bevorzugen, weil das so in einer anderen Sprache gelöst würde. Ich sehe keinen Grund, warum du hier Laufzeitpolymorphie brauchen solltest, du etwa?
Und um das klar zu stellen ich bevorzuge templates nicht wegen eines möglichen Geschwindigkeitsvorteiles, sondern weil sie einfach mehr Sinn machen. Wenn ich irgendwo Laufzeitpolymorphie sehe, dann gehe ich davon aus, dass ich die auch benutzen kann/soll, was in deinem Beispiel nicht der Fall ist. Wenn ich ein template sehe, dann weiss ich sofort, dass da die Auswahl nicht zur Laufzeit geschen soll, sondern zur Kompilierzeit.
Ich denke ich werde mich mit dem template-ansatz begnügnen...
Das klingt irgendwie so, als würdest du templates nicht gerne benutzen und sie eher vermieden werden sollen und das jeder andere Ansatz wahrscheinlich besser ist. Ich empfinde templates als sehr mächtiges Werkzeut, welches ich sogar lieber einsetze (wenn möglich), als Laufzeitpolymorphie. Templates verlangen keine so hohe Kopplung der Klasse mit einem Basistypen, damit die Polymorphie klappt und das ist einer der Punkte, was ich sehr schätze.
-
Danke für die ausführliche Hilfe und Beschreibung.
Ich hoffe es ist kein falscher Eindruck entstanden - ich benutze Templates sogar gern wenn es denn sinnvoll ist. In diesem neuen Zusammenhang sehe ich (jetzt) sogar dass es sinnvoll wird. In diesem Sinne möchte ich für die Hinweise danken!Wie sieht es nun mit folgendem aus: Ich möchte 2 Vektoren abhängig vom user-input entweder mit double-werten oder mit complexen (2 doubles pro element) füllen. dies z.B. über ein einfaches inputargument.
Zusätzlich soll anschließend je nach typ dann ausgelesen werden z.B. also template-spezifisch. Aber später dann auch templatebasiert für beide eingaben nur 1 code. wäre hier LaufzeitPolymorphie sinnvoller als dieser ansatz:Ist alles nur pseudocode und der sinn ist auch fraglich - es geht mir nur ums prinzip hier:
//Vorsicht das ist nur pseudocode Vector<double>* vec_double = NULL; Vector<complex>* vec_complex = NULL; int main(int argc, int** argv) { // der eingabetyp wird aus argv ausgelesen und steht nun // in inputtype. if (inputtype == 0) { vec_double = new Vector<double>(mysize); // Jetzt soll eine ausgabe kommen die in einer klasse steht Output out; out.printvector(vec_double); //Jetzt kommt eine methode die templatebasiert in einer templatebasierten klasse ist: T_Class tc; double nbr = tc.nbrelements(vec_double); } else { vec_complex = new Vector<complex>(mysize); // Jetzt soll eine ausgabe kommen die in einer klasse steht Output out; out.printvector(vec_double); //Jetzt kommt eine methode die templatebasiert in einer templatebasierten klasse ist: T_Class tc; int nbr = tc.nbrelements(vec_double); } if(vec_double) delete vec_double; if(vec_complex) delete vec_complex; } class Output { public: void printvector(Vector<double> *vec) { //angenommen die size ist bekannt for (int i = 0; i < size; i++) { std::cout << vec[i] << std::endl; } } void printvector(Vector<complex> *vec) { //angenommen die size ist bekannt for (int i = 0; i < size; i++) { std::cout << vec[i].real << vec[i].imag << std::endl; } } } template <class T> class T_Class { public: int nbrelements (Vector<T>* vec_double); } template <class T> int T_Class<T>::nbrelements(Vector<T>* vec) { nbr = 0; //Angenommen die size ist bekannt for (int i=0; i < size; i++) nbr += 1; return nbr; }
-
was spricht dagegen, complex<double> zu nutzen? den imaginärteil kann man ja auch 0 setzen^^
bb
edit:
template <class T> int T_Class<T>::nbrelements(Vector<T>* vec) { nbr = 0; //Angenommen die size ist bekannt for (int i=0; i < size; i++) nbr += 1; return nbr; }
?
vec->size() ?
allg. find ich den code komisch...
erst recht für nur pseudo-code...bb
-
Ok - nehmen wir mal an man würde unbedingt zwei typen wollen - double und complex. Mir gehts hier nur um das prinzip bwzüglich templats und Laufzeitpolymoprhie...
dass der code komisch ist hab ich ja schon geschrieben....
Eine Zweite Frage die ich noch nicht verstehe:
Bei dem template-Beispiel von Drakon wird ja sowohl die KLasse sequential als auch Parallel kompiliert.Ich will aber in den Methoden der Klasse Parallel Bibliotheksaufrufe benutzen die
die Klasse Sequential nicht hat. Dazu inkludiere ich in der Klasse Parallel einfach die entsprechenden Header. Kompiliere ich mit meinem DParallel flag läuft alles. Die parallele version zu erstellen geht also. Kompiliere ich jedoch ohne flag meckert der Compiler dass er die Library-funktionen nicht kennt - klar - es wird auch mit nem anderen Compiler kompiliert (g++ statt wie im parallelen mit mpicxx z.B).Und wie kann ich das jetzt an meine Templates anpassen? Es sieht jetzt ganz danach aus als müsste ich jeden bibliotheksaufruf in der klasse Parallel in ein #ifndef packen...? Stimmt das?
-
über kurze Hilfe würde ich mich freuen ...
-
Warum so ungeduldig? Dein alter Post ist noch nicht einmal eine Stunde alt.
-
Warum so ungeduldig? Dein alter Post ist noch nicht einmal eine Stunde alt.
falsch - er ist 1 Tag und 1 stunde alt. ungeduldig zwinged ja - weil ich vorwärtskommen will - und das liegt einzigund allein an mir ich weiß
-
Huch, in der Tat. Sorry dann, habe nur auf die Zeit geschaut.
-
Und wie kann ich das jetzt an meine Templates anpassen? Es sieht jetzt ganz danach aus als müsste ich jeden bibliotheksaufruf in der klasse Parallel in ein #ifndef packen...? Stimmt das?
Ja. Du willst ja, dass der Compiler den Code gar nicht erst sieht und das kannst du ja nur vor dem Kompilierungsprozess machen, also musst du da mit den defines arbeiten.
-
Ich will auch versuchen, dir zu helfen.
Mati schrieb:
Wie sieht es nun mit folgendem aus: Ich möchte 2 Vektoren abhängig vom user-input entweder mit double-werten oder mit complexen (2 doubles pro element) füllen. dies z.B. über ein einfaches inputargument.
Hier ist die Information doch erst zur Laufzeit bekannt, oder? (Falls nicht, was meinst du mit user-input?)
Dann würden dir Templates nicht viel helfen. Könntest du nicht eine gemeinsame Basisklasse für die beiden Typen einrichten und in der die Aktionen, die beide haben sollen, vereinheitlichen?
class Base { public: virtual ~Base(); // hier die gemeinsamen Methoden, z.B. virtual void print() = 0; }; class Double : public Base // Wrapper um double { public: Double(double value) : value(value) virtual void print() { std::cout << "Double: " << value << "\n"; } private: double value; }; class Complex : public Base // Wrapper um std::complex { public: Complex(const std::complex<double>& value) : value(value) {} virtual void print() { std::cout << "Complex: " << value.real() << " + " << value.imag() << "i\n; } private: std::complex<double> value; };
Dann könntest du in einem if entscheiden, welcher Typ nun erstellt werden soll:
Base* zahl; if (doubleEingegeben()) zahl = new Double(...); else zahl = new Complex(...); // speichere in Container oder so delete zahl;
-
hmmm....das versteh ich net....bei dem beispiel von dir drakon wollte ich es so machen aber da geht so nicht:
class sequential { void do_this_stuff (int p /*...*/ ) { // mach was.. } }; class parallel { void do_this_stuff (int p /*...*/ ) { //Das ist der Library call MPI_Comm_Rank(); } }; template<typename technique> class manager { void general_stuff () { // mach allgemeines } void special () { technique t; t.do_this_stuff ( 2 ); } }; int main { #ifdef DParallel manager<parallel> m; #else manager<sequential> m; #endif m.general_stuff (); m.special (); }
Ja. Du willst ja, dass der Compiler den Code gar nicht erst sieht und das kannst du ja nur vor dem Kompilierungsprozess machen, also musst du da mit den defines arbeiten.
Jetzt bin ich verwirrt - ich dachte gerade die #ifdefs auszulagern wäre die idee gewesen - mir ist schon klar dass das so nicht geht aber irgendwie war mein Ziel ein anderes...nur ein einziges mal ein #ifdef sozusagen....
@der hüter der zeit: danke für deinen Vorschlag - ja ich verstehe so ungefähr was du meinst und denke so würde es gehen. Im endeffekt ist es halt aber wieder fast doppelter code um den man aber wohl nicht drumrum kommt danke schonmal!
-
Wenn für alle Instanzen immer das gleiche Verhalten benötigt wird, würde ich gar nicht erst Templates nehmen.
class manager { public: void general_stuff() { } void special { #ifdef DParallel parallel_special(); // die Aktionen bei parallel #else sequential_special(); // die Aktionen bei sequentiell #endif } };
Tut mir leid, wenn wir etwas aneinander vorbeireden... Evtl. versteh ich dein Problem auch falsch
-
hmm....ja ich bin am überlegen ob es denn noch alternativen gibt bezüglich des compilezeit polymorphismumses... danke für den weiteren vorschlag
du meintest dann wohl eher sowas oder?
class manager { public: void general_stuff() { } void special { #ifdef DParallel Parallel parallel; parallel.special1(); parallel.special2(); //... #else Sequential sequential; sequential.special1(); sequential.special2(); #endif } };
Obwohl - so ists auch ein schmarrrn.....herrgott....