Template Frage - design: Input-Anhängig?
-
Danke CStoll...
...ich will natürlich nicht anfangen zu coden wenns im nachhinein nicht gehen wird - deshalb bin ich jetzt etwas skeptisch :):ich werde später dann mal ne bibliotheksfunktion aufrufen bzw. 2 funktionen die abhängig vom template typ sind. Das ist ja machbar durch überladung z.B.
Also mein Vorgehen wäre jetzt so:
Ich laufe erstmal zur einlese funktion und lese in der ersten zeile des files z.B ein ob es ein real oder complex file ist. Jetzt springe ich abhängig von dieser eingabe an eine andere einlese-funktion und generierer mir meine spezielle datenstruktur die an den typ des inputs gebunden ist. (Z.B ein double array bei real und eine srtuktur (real, imag) bei complex).
So, und ab jetzt übergebe ich meine Datenstruktur an meinen algorithmus der über ein template alles regelt.....das müsste doch gehen oder?Danke
-
Hi,
wie von Dir schon gesagt: templates werden nicht zur Lauf-, sondern bereits zur Compilezeit aufgelöst.
Für mich wäre die entscheidende Frage: Sollen alle Funktionen (oder mindestens die meisten) dieselbe Implementierung haben ?
Andernfalls bieten sich jedenfalls Overloads genauso an (sind aber einfacher zu handlen).Beispiel, wo templates IMO sinnvoll sind
// overloadversion int add(int a, int b) { return a + b; } double add(double a, double b) { return i1 + i2; } long add(long a, long b) { return i1 + i2; } // => viel einfacher mit template template <typename T> T add(T a, T b) { return a + b; }Gegenbeispiel:
// template-Version template <typename T> T append(T a, T b); template <> // Spezialisierung für int int append(int a, int b) { return a*100 + b; } template <> // Spezialisierung für string string append(string a, string b) { return a + b; } template <> // Spezialisierung für ostream ostream append(ostream a, ostream b) { return a << b; } // overloadversion ist genauso gut aber kürzer int append(int a, int b) { return a*100 + b; } string append(string a, string b) { return a + b; } ostream append(ostream a, ostream b) { return a << b; }Bei beiden Verfahren wird aber schon zur Compilezeit festgelegt, welche Funktion mit dem Call verknüpft wird (und sei es, dass man mittels RTTI ein "switch-case" aufbaut).
void doit_fromString(string input) { switch(input[0]) { case 'i': doit(atoi(input.c_str()+1)); // fest mit doit(int) verknüpft case 'd': doit(atod(input.c_str()+1)); // fest mit doit(double) verknüpft case 'l': doit(atol(input.c_str()+1)); // fest mit doit(long) verknüpft } }Wirkliche "Laufzeitentscheidungen" kannst Du eigentlich nur mit "virtual" hinbekommen:
struct Base { virtual void doit(); virtual ~Base() {} }; struct D1 { void doit(); }; struct D2 { void doit(); }; void doit_fromBase(Base* input) { input[0]->doit(); // wird erst anhand des konkreten Objektes ausgewählt }Gruß,
Simon2.
-
Du wirst ja auch im ganzen restlichen Programm (oder in einem vielleicht nicht so kleinen Teil davon) mit der complex-liste arbeiten oder mit der double-liste, je nachdem. Du koenntest einen abstrakten Listenverarbeiter definieren und von dem die konkreten Verarbeiter ableiten, die dann die Typspezifischen Einzelheiten implementieren:
class AbstractListVerwurster { public: virtual ~AbstractListVerwurster() {} = 0; virtual void input(std::istream& is) = 0; virtual void output(std::ostream& os) = 0; };Fuer weitgehend normale Daten kannst du dann ein Klassentemplate definieren, das davon ableitet:
template <typename T> class EasyListVerwurster : public AbstractListVerwurster { public: virtual void input(std::istream& is) { //standard eingabeprozedur } virtual void output(std::ostream& os) { //standard ausgabeprozedur } private: std::list<T> list_; };Und im Programm dann:
AbstractListVerwurster *wurstl; if (/*Dateiinhalt ist double*/) wurstl = new EasyListVerwurster<double>; else if (/*Dateiinhalt ist complex*/) wurstl = new EasyListVerwurster<complex>; wurstl->input(datei); wurstl->output(std::cout);Wenn du das Ganze auch noch fuer eine selbstgeschriebene KLasse machen moechtest, wo z.B. das Einlesen udn die Ausgabe nicht so einfach sind, kannst du
a) Eine Spezialisierung EasyListVerwurster<MySpecialClass> definieren oder
b) Eine andere Verarbeiterklasse von AbstractListVerwurster ableiten - die Moeglichkeiten auch fuer verschiedene Verarbeitungsmethoden deiner doubles sind quasi unbegrenzt.
-
Danke für eure großzügige Hilfe!
Also prinzipiell werde ich lediglich 2 Inputs haben - wie schon gesagt: double und complex also 2 double werte.
Prinzipiell läuft der Algorithmus mit allen funktionen dann identisch ab, nur an einigen wenigen stellen werden dann halt 2 double werte übergeben wenns complex ist bzw. nur ein double wert wenns real ist.
Ich glaube overloading würde den code ziemlich aufblähen und fast doppelt halten dann oder?
Meine Frage ist auch: Polymorphie ist an dieser stelle nicht unbedingt nötig oder? Weil ich weiß ja welcher input dann kommen wird und dann alloziiere ich halt abhängig z.B nicht ein double* arr = new double, sondern halt ein complex* arr = new complex oder sowas....
-
testo schrieb:
Meine Frage ist auch: Polymorphie ist an dieser stelle nicht unbedingt nötig oder? Weil ich weiß ja welcher input dann kommen wird und dann alloziiere ich halt abhängig z.B nicht ein double* arr = new double, sondern halt ein complex* arr = new complex oder sowas....
Schon, nur musst du dann beim Einlesen die unterscheidung machen, bei Ausgaben die unterscheidung machen, eventuell bei irgendwelchen Rechnungen die Unterscheidung machen usw. Du kannst nichtmal deine Bibliotheksfunktionen ohne if-else staement aufrufen, weil du ja vermutlich die Daten in einen Container liest und dann jeweils entsacheiden musst, in welchem der beiden Container (die auch noch nebeneinander existieren muessten) diene Werte nu eigentlich drin sind.
Du musst die ganze Zeit ueber die Typunterscheidung zu fuss machen, waehrend polymorphie doch gerade dazu da ist
-

ich habe jetzt noch genauer hingeschaut in meinem code....es wird sich im endeffekt nur ein array ändern - klar das was die werte trägt
ich habe eine Klasse Matrix die ein Pointer auf eine Klasse Lines hat. Lines hat jetzt das array das die werte trägt vals. vals wird also entweder ein double array sein oder eben ein array mit elementen die ein struktur complex sein werden.Ich übergebe immer die Matrix als pointer und greife auf die werte so zu:
Matrix->lines->vals[i];wenn ich jetzt z.B einen teil in ein anderes array kopieren wollte - muss ich ja das neue array vom gleichen typ anlegen und auch die methode die den Pointer Matrix bekommt muss ja dann intern abhängig vom typ unterschiedlich arbeiten.
Was wäre jetzt die korrektere/einfachere Lösung?
-
Also ich poste mal nen einfachen beispielcode der von der struktur her ähnlich ist:
//main: Matrix *A = NULL; Read::read(A); Compute::algorithm(A);Read::read(Matrix *&A) { //liest input file in Datenstruktur A ein: A->anzahl_zeilen = anzahl_zeilen_aus_file; foreach(//zeile in file) A->lines->val[i] = double_aus_file }Compute::algorithm(Matrix *&A) { double * arr = NULL; arr = copy_to_arr(A); //Jetzt kommt der bibliotheksaufruf der abhängig ist von dem typ if (//type = double) bibl_1(arr); else //type = complex bibl_2(arr); } double* Compute::copy_to_arr(Matrix *&A) { double *arr = new double[dim]; //kopiere einen teil aus A->lines->val nach arr return arr; }class Matrix { public: Lines *lines; int anzahl_zeilen; };vals sollte dann je nach input nicht nur ein double wert enthalten sondern auch z.B die structur-elemente complex.
und das würde ich gerne auf einen complex input erweitern, also so dass ich z.B auch in der Methode copy_to_arr() aus vals dann spezifisch halt die complex-werte auslesen kann.
-
hmm...ich bräuchte eigentlich schon noch hilfe...
-
Wenn ich dich richtig verstehe ist das relativ einfach zu lösen. Und zwar machst du einfach aus allen Programmteilen die mit den doubles umgehen Templates, und aus der main Funktion auch. Dort wo es Unterschiede gibt spezialisierst du einfach.
Die eigentliche main Funktion guckt sich dann bloss das File an, und ruft je nachdem was drinnen steht die "double" oder "complex" Variante auf:void main() { file f(...); if (is_complex_input_file(f)) my_main<std::complex<double> >(f); else my_main<double>(f); }Dadurch hast du bloss ein einziges "if" ganz zu beginn des Programms, alles andere ist statisch gebunden.
Das führt zwar dazu dass im Prinzip der gesamte Code verdoppelt wird, dein Programm also deutlich grösser werden wird, aber das sollte schätze ich mal egal sein.
-
....ja...danke - ich hoffe du hast dir meinen letzten post angeschaut? gäbe es evtl. irgendwie eine möglichkeit die verdopplung einzuschränken? aber auch wenn ich polymorph arbeiten würde hätte ich ja eine verdopplung, denn die methoden die polymorph wären müsste ich ja dann auch doppelt schreiben.
Also prinzipiell macht das doppelte code nichts aus. Hauptsache ist dass es performant bleibt - was es ja durch eine einzige if-abfrage auch bleibt...
für weitere Stellungnahmen bin ich gerne offen
Danke an dieser stelle
-
testo schrieb:
....ja...danke - ich hoffe du hast dir meinen letzten post angeschaut?
Ja, hab' ich. Allerdings würde ich "read" und "algorithm" einfach als Template-Funktionen realisieren (nicht als Template-Klassen), vorausgesetzt sie sind "stateless". Und der Code sieht für mich nicht sehr sauber aus, deswegen bin ich nicht genauer darauf eingegangen

gäbe es evtl. irgendwie eine möglichkeit die verdopplung einzuschränken?
Ja, gibt es. Du kannst Teile die komplett unabhängig vom Typ sind als normale Funktionen implementieren, bzw. wenns um Memberfunktionen geht diese in eine Basisklasse schieben von der du die Template-Klasse dann ableitest. Oft geht das aber nur an wenigen Stellen (mit ganz kleinen Funktionen), und in dem Fall verzichtet man meist einfach darauf.
aber auch wenn ich polymorph arbeiten würde hätte ich ja eine verdopplung, denn die methoden die polymorph wären müsste ich ja dann auch doppelt schreiben.
Wenn du polymorph arbeitest ergibt sich oft eine Grenze, ab der es keine Verdoppelung mehr gibt. Wenn du z.B. ein Array aus Zeigern auf "comparable" sortierst (wobei comparable::is_less virtual ist und zum Vergleich verwendet wird), dann ist das eine einzige "sort_comparable" Funktion für alle comparable. Wenn du es über Templates machst sind es verschiedene Instanzierungen des "sort" Templates. Das macht oft einen riesen Unterschied.
EDIT:
polymorph wird also nur "is_less" verdoppelt.Mit Templates dagegen wird is_less, sort und jede Funktion die sort aufruft verdoppelt (da alle Funktionen die sort aufrufen wollen selbst Templates sein müssen, sonst könnten sie ohne runtime polymorphismus nicht mit unterschiedlichen Typen arbeiten).
/EDITAlso prinzipiell macht das doppelte code nichts aus. Hauptsache ist dass es performant bleibt - was es ja durch eine einzige if-abfrage auch bleibt...
Dann würde ich es über Templates machen und auf keinen Fall irgendwo Runtime Polymorphismus verwenden. Dass das Programm dadurch grösser wird wird in dem Fall nicht stören, da die (bei einem Durchlauf) nicht verwendeten Funktionen eben nicht verwendet werden, also nicht im Cache landen, und daher auch nicht "bremsen" können.
für weitere Stellungnahmen bin ich gerne offen
Danke an dieser stelleBüdde gerne

-
puh...also es funktioniert schon so....aber ich glaube ich muss sehr sehr viel code doppelt schreiben.
also ich versuche ein einfaches beispiel und würde gerne wissen ob es keinen weg daran vorbei gibt:
ich habe eine template class. In dieser klasse wird abhängig vom input z.B ein array gefüllt mit objekten.
template <class T> class my_class { T* arr; }wenn jetzt im input file z.B nur reelle werte stehen , dann soll das array mit doubles gefüllt werden , es wird also ein objekt mit my_class<double> erstellt.
falls z.B complex der input ist dann halt ein objekt my_class<complex> erstellt.
jetzt muss ich aber den kompletten algorithmus doppelt schreiben weil ja die datenstruktur unterschiedlich. ist. ALso überall wo z.B auf das arr zugegriffen wird muss der code ja doppelt sein da T immer anders ist.
Also z.B um die werte aus arr auszugeben muss ich 2 Methoden schreiben die z.B nur double werte ausgeben oder aus der complex - structure die jeweils 2 double werte rausholen.Stimmt dieses vorgehen bzw. meine idee/implementierung soweit?