Einführung in die Programmierung mit Templates



  • Hallo (sorry für die lange Reaktionszeit),

    hagman schrieb:

    Genau das meine ich. Hast du min.hpp im selben Programm noch in eine andere Übersetzunseinheit inkludiert?
    Das hätte ich vielleicht noch erwähnen sollen.

    Yo, dann haben wir ein Problem mit unserem Freund, dem Linker. Aber das macht nichts, denn anstatt die Spezialisierung static zu machen (oder inline, was manchmal einfach nicht geht), setzen wir sie in einen (anonymen) namespace:

    //min.hpp
    #ifndef MIN_HPP
    #define MIN_HPP
    
    namespace {
    
    template <typename T>
    const T& minimum(const T &a, const T &b) {
      return a < b ? a:b;
    };
    
    const char* minimum(const char *str1, const char *str2) {
      return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
    };
    
    };  //namespace
    
    #endif //MIN_HPP
    

    Jetzt alles klar?

    @ness
    Auch deine Anmerkung wird Einzug im Artikel finden, danke dir für den Hinweis.

    Mfg

    GPC



  • 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 sein

    Vielleicht 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 sein

    Vielleicht 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.


Anmelden zum Antworten