Einführung in die Programmierung mit Templates
-
naja, aber der anonyme namespace ist doch eine ziemlich "unsaubere" Lösung, finde ich. Entweder du wilst die funktionen inlinen, dann benutz das schlüsselwort, oder eine vollständig spezialisierte funktion gehört in eine implementierungsdatei.
-
Hallo,
ness schrieb:
naja, aber der anonyme namespace ist doch eine ziemlich "unsaubere" Lösung, finde ich. Entweder du wilst die funktionen inlinen, dann benutz das schlüsselwort, oder eine vollständig spezialisierte funktion gehört in eine implementierungsdatei.
das kann man jetzt halten, wie man will. Für gewöhnlich lagere ich die spezialisierte Funktion auch in eine Implementationsdatei aus, oft habe ich aber schon benannte Namensräume, und dann lass ich sie einfach im Header.
Btw. Was findest du, ist unsauber daran?
mfg
GPC
-
naja, weil du dann den code mehrmals (in jeder objektdatei) hast. In dem sinne kannst du fast alles in unbenannte namespace packen.
-
Hi,
ness schrieb:
naja, weil du dann den code mehrmals (in jeder objektdatei) hast. In dem sinne kannst du fast alles in unbenannte namespace packen.
stimmt, das ist bei größeren Funktionen/Klassen ein Argument. Werd mich über's WE mal ransetzen, sofern ich Zeit finde.
Mfg
GPC
-
Hi, cooler Artikel ( nochmal Grundlagen aufgefrischt )
GPC schrieb:
Selbstverständlich kann man auch mehrere Template-Parameter angeben:
//Zwei Parameter, einer vom Typ T und einer vom Typ U template <class T, class U> ... template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int ...
Für "nicht-Typ-Parameter", also built-ins, gelten folgende Einschränkungen:
1. Sie dürfen nicht verändert werden
2. Sie dürfen nur ganzzahlig seinVielleicht sollte man hier auch noch erwähnen das built-ins als template paramterer als Konstanten betrachtet werden(Bei generischen Klassen).
Weil sie zur Compilezeit bekannt sind un man sie dadurch als Größenangabe für Array's verwenden kann und sie auch nicht verändert werden können.
-
Hallo,
Freak_Coder schrieb:
Hi, cooler Artikel ( nochmal Grundlagen aufgefrischt )
Vielen Dank.
GPC schrieb:
Selbstverständlich kann man auch mehrere Template-Parameter angeben:
//Zwei Parameter, einer vom Typ T und einer vom Typ U template <class T, class U> ... template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int ...
Für "nicht-Typ-Parameter", also built-ins, gelten folgende Einschränkungen:
1. Sie dürfen nicht verändert werden
2. Sie dürfen nur ganzzahlig seinVielleicht sollte man hier auch noch erwähnen das built-ins als template paramterer als Konstanten betrachtet werden(Bei generischen Klassen).
Dachte eigentlich, das sei klar, nachdem ich erwähnt habe, dass eine Auswertung zur Compilezeit erfolgt.
Weil sie zur Compilezeit bekannt sind un man sie dadurch als Größenangabe für Array's verwenden kann und sie auch nicht verändert werden können.
Aber ich pack's noch dazu, damit alle Klarheiten beseitigt sind.
Mfg
GPC
-
Guter Artikel
Allerdings komme ich bei folgendem nicht ganz mit:
GPC schrieb:
Bei den vorangegangenen Beispielen war es noch nicht notwendig, aber wenn man größere Klassen oder Bibliotheken implementiert, dann möchte man die Schnittstelle in eine .hpp-Datei und die Implementation in eine .cpp-Datei schreiben.
Die Motivation dahinter ist, dass jede Änderung an einer Headerdatei dazu führt, dass all der Code neu kompiliert werden muss, der diese Headerdatei benutzt. Gerade in Projekten mit vielen Dateien löst das große Neu-Kompilier-Wellen aus, die ziemlich lange dauern können.Leider geht das bei Templates so nicht. Das liegt daran, dass das Template erst überall dort in den Code eingesetzt wird, wo es auch verwendet wird. Vorher ist es "nur ein Stück Text", erst beim Einsetzen bekommt es seine Bedeutung. Genau das verursacht aber das oben beschriebene Verhalten vom Neu-Kompilieren.
Und genau das ist doch auch erwünscht, wenn ich ein Template ändere, oder nicht? Mir ist deswegen nicht ganz klar, warum man Teile der Template-Implementierung (die effektiv Teil des Templates sind) irgendwohin auslagern wollen könnte. Um auf das verwendete Beispiel einzugehen:
//stack.hpp template <typename T> class Stack { //wie oben, nur die Schnittstelle }; //Achtung: #include "stack.impl"
//stack.impl template<typename T> inline bool Stack<T>::empty() const { return (tip==0) ? true : false; }; //...
"Normalerweise" hätte man die Template-Funktion Stack<>::empty() ebenfalls im Header stack.hpp. Ändere ich nun den, wird jede .cpp-Datei neu kompiliert, die diesen Header eingefügt hat, weil sich ja etwas für den Quellcode interessantes geändert haben könnte. Mit der gezeigten Methode, eine Datei stack.impl zu benutzen, könnte ich stack.impl ändern und (sofern ich mein Build-System davon nicht benachrichtigt habe) mir einen Wolf kompilieren, ohne daß die Änderungen wirksam werden, weil das Änderungsdatum des Headers nicht neuer ist als das der kompilierten Objekte.
Genauso kann ich aber etwas an stack.hpp ändern und werde trotzdem den Rattenschwanz der stack.impl hinter mir herziehen, weil dann mein Build-System merkt: "Aha, Header neuer als Objekt, Kompilation anstoßen", der Compiler (Präprozessor) wird merken, daß da eine eigenartige stack.impl eingefügt wird und diese korrekterweise in den header einfügen, worauf dann der Compiler wieder den vollständigen Template-Code "sieht" und das Template so oft instanziiert, wie es nunmal nötig ist.
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen).
-
tommie-lie schrieb:
Guter Artikel
Danke.
Und genau das ist doch auch erwünscht, wenn ich ein Template ändere, oder nicht?
Hat sich was geändert, soll es neu kompiliert werden, korrekt.
"Normalerweise" hätte man die Template-Funktion Stack<>::empty() ebenfalls im Header stack.hpp. Ändere ich nun den, wird jede .cpp-Datei neu kompiliert, die diesen Header eingefügt hat, weil sich ja etwas für den Quellcode interessantes geändert haben könnte. Mit der gezeigten Methode, eine Datei stack.impl zu benutzen, könnte ich stack.impl ändern und (sofern ich mein Build-System davon nicht benachrichtigt habe) mir einen Wolf kompilieren, ohne daß die Änderungen wirksam werden, weil das Änderungsdatum des Headers nicht neuer ist als das der kompilierten Objekte.
Die Trennung ist in dem Fall auch rein optischer Natur, denn am Ende inkludiere ich ja die cpp datei in die hpp Datei und damit bin ich gleich weit, wie wenn ich alles in die hpp Datei geschrieben hätte. Es ist nur eine Erleichterung der Übersicht.
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen).
Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben? Egal.
MfG
GPC
-
GPC schrieb:
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen).
Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben?
Klang für mich nicht so. In den ersten beiden Absätzen beschwerst du (oder 7H3 N4C3R) dich über die mitunter längeren Kompilierungszyklen, die entstehen, wenn man seine 100 Templates neu durch den Compiler jagt. Ich dachte, daß damit auch Augenmerk auf die Performance gelegt werden soll und die beschriebene Lösung die Performance verbessern soll.
Aber der letzte Satz des Absatzes sagt ja, daß man dmait die Schnittstelle von der Implementierung trennt, vielleicht hätte ich mehr auf den Satz als auf die ersten beiden Absätze hören sollen
Aber vielleicht sollte man über eine Warnung nachdenken, daß man sein Build-System irgendwie davon überzeugen sollte, daß stack.impl nun ebenfalls Teil des entsprechenden Moduls ist. Ich denke nicht, daß die GNU autotools da ohne Handarbeit automatisch drauf reagieren.
-
Hallo,
tommie-lie schrieb:
GPC schrieb:
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen).
Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben?
Klang für mich nicht so. In den ersten beiden Absätzen beschwerst du (oder 7H3 N4C3R) dich über die mitunter längeren Kompilierungszyklen, die entstehen, wenn man seine 100 Templates neu durch den Compiler jagt. Ich dachte, daß damit auch Augenmerk auf die Performance gelegt werden soll und die beschriebene Lösung die Performance verbessern soll.
schön wär's. Aber leider hat das performancetechnisch keine Auswirkungen.
Aber der letzte Satz des Absatzes sagt ja, daß man dmait die Schnittstelle von der Implementierung trennt, vielleicht hätte ich mehr auf den Satz als auf die ersten beiden Absätze hören sollen
kein Thema.
Aber vielleicht sollte man über eine Warnung nachdenken, daß man sein Build-System irgendwie davon überzeugen sollte, daß stack.impl nun ebenfalls Teil des entsprechenden Moduls ist. Ich denke nicht, daß die GNU autotools da ohne Handarbeit automatisch drauf reagieren.
äh, nein. Das tun sie nicht. denn die impl Datei ist ja nicht "direkt" am Build-Prozess beteiligt. Du siehst, die impl-Lösung zieht nen Rattenschwanz von Problemen nach sich. Ich pers. verzichte auf diese Möglichkeit und knall alles direkt in den Header.
MfG
GPC
-
GPC schrieb:
Du siehst, die impl-Lösung zieht nen Rattenschwanz von Problemen nach sich.
Ich weiß
GPC schrieb:
Ich pers. verzichte auf diese Möglichkeit und knall alles direkt in den Header.
Ich auch
-
haha, okay, dann sind wir uns ja einig...
-
GPC schrieb:
haha, okay, dann sind wir uns ja einig...
Jupp. Ich bin aufgrund des ersten Absatzes nur mit ganz anderen Vorstellung an diesen Abschnitt des Tutorials herangegangen und dachte, du würdest ernsthaft denken, daß dieses Verfahren die Performance verbessert. Da dem nicht so ist (beides, es verbessert du Performance nicht und du denkst auch nicht, daß es das tun würde), bleibt die Diskussion nur eine Anmerkung für alle weiteren Leser.
-
Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen. Also sowas in der Art
template<typename T> void f() { if (typename(T)==TypeInt) T++; foo(T); }
danke
-
_et schrieb:
Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen. Also sowas in der Art
template<typename T> void f() { if (typename(T)==TypeInt) T++; foo(T); }
danke
Joah, du kannst es über Spezialisierungen (Abschnitt 5 bzw. 6 für Klassen) machen, aber mit ner if-Abfrage wird das nichts.
MfG
GPC
-
_et schrieb:
Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen.
Gehen tut das sicherlich so ähnlich wie du es geschrieben hast, aber der übliche Weg ist es, seine Templates zu spezialisieren:
template<typename T> void foo(T arg1) { // generische Implementierung für alle Typen } template<> void foo(int arg1) { // spezielle Implementierung die nur für ints gilt }
Syntaktisch also genauso aufgebaut wie Überladen von Funktionen.
Beachte dazu aber auch den Artikel Why not specialize function templates?, die Templateauflösung von C++ ist nicht immer so intuitiv, wie du es dir denkst.Edit: Zu spät...
-
Danke
-
tommie-lie schrieb:
Beachte dazu aber auch den Artikel Why not specialize function templates?, die Templateauflösung von C++ ist nicht immer so intuitiv, wie du es dir denkst.
Wirklich ein sehr guter Artikel. Evtl. werd ich noch Links auf die wichtigsten Artikel von Sutter, Meyers usw. in meinen Artikel reinpacken, wäre bestimmt nicht verkehrt.
MfG
GPC
-
GPC schrieb:
Evtl. werd ich noch Links auf die wichtigsten Artikel von Sutter, Meyers usw. in meinen Artikel reinpacken, wäre bestimmt nicht verkehrt.
Naja, zuviel Sekundärliteratur kann auch erschlagen
Aber was mir jetzt erst in deinem Artikel auffällt:
//Ermittelt das Minimum aus a und b template <typename T> inline const T& minimum(const T &a, const T &b) { return a < b ? a:b; }; //Spezialisierung für C-Strings inline const char* minimum(const char *str1, const char *str2) { return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 ); };
Das ist keine Spezialisierung, das ist ein Überladen der Funktion. Und der Code ist auch nicht, wie im Artikel beschrieben, nicht ANSI-konform. Das ist er und ein moderner Compiler sollte keinerlei Fehlermeldung ausgeben (der GCC 4.0 gibt nichtmal eine Warnung aus). Die Deklaration mit und ohne den Modifier "template" macht einen semantischen Unterschied, nämlich den zwischen einer überladenen Funktion und einem überladenen Template.
Ich denke das sollte man nochmal irgendwie klarstellen.
-
tommie-lie schrieb:
Aber was mir jetzt erst in deinem Artikel auffällt:
//Ermittelt das Minimum aus a und b template <typename T> inline const T& minimum(const T &a, const T &b) { return a < b ? a:b; }; //Spezialisierung für C-Strings inline const char* minimum(const char *str1, const char *str2) { return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 ); };
Das ist keine Spezialisierung, das ist ein Überladen der Funktion.
Steht im Satz drunter:;
GPC schrieb:
Eigentlich haben wir jetzt die Funktion minimum überladen, um unser Ziel, also die Spezialisierung, zu erreichen.
Und der Code ist auch nicht, wie im Artikel beschrieben, nicht ANSI-konform. Das ist er und ein moderner Compiler sollte keinerlei Fehlermeldung ausgeben (der GCC 4.0 gibt nichtmal eine Warnung aus).
IIRC macht auch der gcc 3.3.6 keinen Stress, dennoch ist der Code imho nicht ANSI konform, ich werde meine Quelle nachschlagen und posten, voraussichtlich morgen.
MfG
GPC