Einfaches Klassentemplate kompiliert nicht


  • Mod

    Der Compiler muss noch wissen, worauf sich die Funktionsdeklarationen überhaupt beziehen, das heißt, du musst jeweils

    template<typename T> Point<T>::Point() : x(0), y(0) {}
    

    und so weiter schreiben.

    Aber:
    Das sieht mir aus, als wolltest du die Auftrennung in Header und Quellcodedatei vorbereiten. Das funktioniert nicht. Für Templates muss die Definition stets dort vorliegen, wo sie benutzt wird (es gibt noch ein paar andere Möglichektien, die hier nicht wichtig sind). Das heißt, das muss sowieso alles in den Header. Ich würde es daher gleich in die Klassendefinition schreiben:

    template<class T>
    class Point {
    public:
        T x, y;
        Point()  : x(0), y(0) {}
        Point(T _X, T _Y) : x(_X), y(_Y) {}
        ~Point(){};
    };
    

    Aber:
    Den unbenutzten Destruktor lassen wir gleich weg und gewöhnen uns in Zukunft an, diese besonderen Funktionen nur zu definieren, wenn uns die compilergenerierte Version nicht ausreicht (was sehr selten sein sollte). Ebenso der schon im Kommentar vorbereitete Zuweisungsgenerator:

    template<class T>
    class Point {
    public:
        T x, y;
        Point()  : x(0), y(0) {}
        Point(T _X, T _Y) : x(_X), y(_Y) {}
    };
    

    Aber:
    Wir denken nochmal über sinnvolle Defaultwerte nach. Einerseits ist

    Point()  : x(0), y(0) {}
        Point(T _X, T _Y) : x(_X), y(_Y) {}
    

    identisch zu

    Point(T _X = 0, T _Y = 0) : x(_X), y(_Y) {}
    

    oder allgemeiner:

    Point(T _X = T(), T _Y = T()) : x(_X), y(_Y) {}
    

    Aber ist das semantisch richtig? Warum ist ein uninitialisierter Punkt Null? Das macht meiner Meinung nach keinen Sinn. Entweder gibt es gar keine uninitialisierten Punkte oder sie sind auch wirklich uninitialisiert. Also

    Point() {}
       Point(T _X, T _Y) : x(_X), y(_Y) {}
    

    oder

    Point(T _X, T _Y) : x(_X), y(_Y) {}
    

    (ich würde Ersteres bevorzugen, kenne aber dein genaues Problem nicht)

    Noch etwas ganz anderes: Guck dir mal an, was endl genau macht. Willst du wirklich flush en oder möchtest du bloß einen Zeilenumbruch ( \n )?

    edit: Und die Bemerkung über mir hinsichtlich der Bezeichner _X und _Y ist natürlich auch richtig und wichtig.



  • SeppJ schrieb:

    Den unbenutzten Destruktor lassen wir gleich weg und gewöhnen uns in Zukunft an, diese besonderen Funktionen nur zu definieren, wenn uns die compilergenerierte Version nicht ausreicht (was sehr selten sein sollte).

    In dieser Allgemeinheit ist das falsch.

    Manche Guidelines raten sogar dazu, immer den leeren Destruktor zu definieren.

    <a href= schrieb:

    http://www.chromium.org/developers/coding-style#TOC-Inline-functions">It can be tempting to inline "empty" constructors and destructors: [...] Don't do this. Implicit initialization of members can result in generating nontrivial amount of code, which then gets duplicated for every new/delete site. Even classes that don't have such members now might have them later, so in general, define these out-of-line


  • Mod

    krom schrieb:

    SeppJ schrieb:

    Den unbenutzten Destruktor lassen wir gleich weg und gewöhnen uns in Zukunft an, diese besonderen Funktionen nur zu definieren, wenn uns die compilergenerierte Version nicht ausreicht (was sehr selten sein sollte).

    In dieser Allgemeinheit ist das falsch.

    Manche Guidelines raten sogar dazu, immer den leeren Destruktor zu definieren.

    <a href= schrieb:

    http://www.chromium.org/developers/coding-style#TOC-Inline-functions">It can be tempting to inline "empty" constructors and destructors: [...] Don't do this. Implicit initialization of members can result in generating nontrivial amount of code, which then gets duplicated for every new/delete site. Even classes that don't have such members now might have them later, so in general, define these out-of-line

    😕 Ich glaube, du hast nicht verstanden, worum es geht.



  • SeppJ schrieb:

    krom schrieb:

    SeppJ schrieb:

    Den unbenutzten Destruktor lassen wir gleich weg und gewöhnen uns in Zukunft an, diese besonderen Funktionen nur zu definieren, wenn uns die compilergenerierte Version nicht ausreicht (was sehr selten sein sollte).

    In dieser Allgemeinheit ist das falsch.

    Manche Guidelines raten sogar dazu, immer den leeren Destruktor zu definieren.

    <a href= schrieb:

    http://www.chromium.org/developers/coding-style#TOC-Inline-functions">It can be tempting to inline "empty" constructors and destructors: [...] Don't do this. Implicit initialization of members can result in generating nontrivial amount of code, which then gets duplicated for every new/delete site. Even classes that don't have such members now might have them later, so in general, define these out-of-line

    😕 Ich glaube, du hast nicht verstanden, worum es geht.

    Doch?
    Du: Es macht nie Sinn, Class::Class(){} zu schreiben, weder im Header noch im cpp, weil das Verhalten das gleiche ist.
    Chromium: Es macht immer Sinn, Class::Class(){} zu schreiben, weil dann die Kompilierzeit verringert wird und das Binary kleiner ist.

    In dem Kontext hier, wo es um Templates geht, macht das natürlich keinen Unterschied, aber allgemein akzeptiert ist deine Aussage nicht.



  • Okay, vielen vielen Dank für die schnelle, kompetente Hilfe ;-).

    Woher soll man denn auch ahnen, dass diese Klassentemplate-Funktionen jetzt auf einmal nicht mehr außerhalb der Klasse definiert werden dürfen 🙄 ?
    Es wird einem doch immer gesagt, zwecks Übersichtlichkeit alles aufzuteilen, woran man sich ja auch schnell gewöhnt...
    Aber gut, wenns mans weiß, weiß mans :-/.

    In dem Beispiel von meinem Prof sind die Funktionen allerdings auch nicht INNERHALB der Klasse definiert, sondern mit

    template<classe T>
    inline Point<T>::Point(T _x, T _y) : x(_x), y(_y) {}
    

    Vlt. repariert sein Visual Studio das ja irgendwie noch nachträglich..?

    Nagut, mir egal - bei mir gehts jetzt


  • Mod

    lukas.braband schrieb:

    Okay, vielen vielen Dank für die schnelle, kompetente Hilfe ;-).

    Woher soll man denn auch ahnen, dass diese Klassentemplate-Funktionen jetzt auf einmal nicht mehr außerhalb der Klasse definiert werden dürfen 🙄 ?

    Das wurde so nicht gesagt (wäre auch falsch). Allerdings muss (im Allgemeinen...) die Definition eines Templates in jeder Übersetzungseinheit vorliegen, in der es benutzt wird. Das schließt i.d.R. die Definition von Templates in einer .cpp-Datei o.ä. aus.
    Die Definition der Memberfunktionen deshalb nicht gleich inline innerhalb der Definition des Klassentemplates erfolgen - das ist aber häuflich bequem und erzeugt weniger syntaktischen Lärm.

    lukas.braband schrieb:

    In dem Beispiel von meinem Prof sind die Funktionen allerdings auch nicht INNERHALB der Klasse definiert, sondern mit

    template<classe T>
    inline Point<T>::Point(T _x, T _y) : x(_x), y(_y) {}
    

    Das ist auch in Ordnung so.



  • und warum funktioniert eine Überladung des <<-Operators bei mir nicht:

    template<class T>
    inline ostream& operator<<(ostream& s, const Point<T>& pt);
    
    template<class T>
    inline ostream& operator<<(ostream& s, const Point<T>& pt) {
    	s << "(" << setw(5) << setprecision(3) << pt.x << " "
    	  << setw(5) << setprecision(3) << pt.y << ")";
    	return s;
    }
    

    😕
    Ist jetzt beides in der Pt.h drin..


  • Mod

    funktioniert nicht
    ist keine Fehlerbeschreibung.



  • Die Überladung des stream Ausgabe-Operators (sowie des Eingabe-Operators) muß eine freie Funktion sein, d.h. lösche die Deklaration innerhalb der Point<T>-Klasse (d.h. lasse nur den Code außerhalb stehen).

    Alternativ kannst/darfst du auch diesen Operator als 'friend' deklarieren (sofern du auf Interna zugreifen willst):

    template<class T>
    class Point
    {
      // ...
    
      template<class T>
      friend ostream& operator<<(ostream& s, const Point<T>& pt);
    };
    
    template<class T>
    inline ostream& operator<<(ostream& s, const Point<T>& pt)
    {
        return s << "(" << setw(5) << setprecision(3) << pt.x << " "
                 << setw(5) << setprecision(3) << pt.y << ")";
    }
    


  • ah, sorry:

    ../src/Point.h:30: error: expected initializer before ‘&’ token
    ../src/Point.h:47: error: expected initializer before ‘&’ token
    

    Und die Funktionen waren eig. schon nicht innerhalb der Klassen-deklaration (nur in der Datei Point.h).
    Und auch als friend-Funktion bleibt der selbe Fehler bestehen 😕 ...



  • #include <ostream> bzw. iosfwd vergessen.



  • Ups, sehe gerade, es muß natürlich überall "std::ostream" heißen (das passiert wenn man einfach Code per C&P hier im Forum übernimmt ;-).

    Und für die Manipulatoren zusätzlich noch <iomanip> einbinden (und auch dort dann "std::" davorsetzen).

    Denn niemals "using namespace std;" auf globaler Ebene im Header einsetzen!!!


Anmelden zum Antworten