Einfaches Klassentemplate kompiliert nicht



  • Hallo,
    ich habe ein Problem, ein einfaches Klassentemplate zum Kompilieren zu bekommen.
    Die Variante der Klasse mit festem Datentyp kompiliert.
    Wahrscheinlich ist es irgendwas total einfaches, aber ich hab jetzt schon ein paar Sachen ausprobiert und den Quelltext eigentlich auch 1:1 von einem angeblich funktionierenden Beispiel abgetippt (und dann später noch ein paar Sachen rausgenommen).
    Hier also der Code:

    #include <iostream>
    using namespace std;
    
    template<class T>
    class Point {
    public:
        T x, y;
        //int x, y;
        Point();
        Point(T _X, T _Y);
        //Point(int _X, int _Y);
        //Point( const Point& pt);
        ~Point();
        //Point& operator=(const Point& pt);
        //bool operator==(const Point& pt) const;
    };
    Point::Point() : x(0), y(0) {}
    //Point::Point(int X, int Y) : x(X), y(Y) {}
    Point::Point(T X, T Y) : x(X), y(Y) {}
    //Point::Point( const Point& pt) : x(pt.x), y(pt.y) {}
    
    Point::~Point() {}
    
    int main() {
      cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
      Point<int> p; // = new Point(1,2);
      //Point* p  = new Point(1,2);
      return 0;
    }
    

    @Edit:
    Ach ja, die Fehlermeldung^^:

    test.cpp:20: error: ‘template<class T> class Point’ used without template parameters
    test.cpp:20: error: ISO C++ forbids declaration of ‘Point’ with no type
    test.cpp: In function ‘int Point()’:
    test.cpp:20: error: ‘int Point()’ redeclared as different kind of symbol
    test.cpp:6: error: previous declaration of ‘template<class T> class Point’
    test.cpp:20: error: only constructors take base initializers
    test.cpp: At global scope:
    test.cpp:22: error: ‘template<class T> class Point’ used without template parameters
    test.cpp:22: error: expected constructor, destructor, or type conversion before ‘(’ token
    test.cpp:25: error: expected constructor, destructor, or type conversion before ‘::’ token
    

    Ich verwende g++ 4.4.3.

    Vielen Dank schonmal im Voraus für eure Hilfe 😉 ,
    Lukas



  • template<class T>
    class Point {
    public:
        T x, y;
        Point();
        Point(T X, T Y);
        ~Point();
    };
    
    template<typename T> Point<T>::Point() : x(0), y(0) {}
    template<typename T> Point<T>::Point(T X, T Y) : x(X), y(Y) {}
    template<typename T> Point<T>::~Point() {}
    
    int main() {
      Point<int> p;
      return 0;
    }
    

    Allerdings gibt es in Templates keinen wirklichen Grund, die Definition der Methoden von der Deklaration der Template zu trennen. Ich würde einfach

    template<class T>
    class Point {
    public:
        T x, y;
        Point()         : x(0), y(0) {}
        Point(T X, T Y) : x(X), y(Y) {}
    
        ~Point() { }
    };
    
    int main() {
      Point<int> p;
      return 0;
    }
    

    schreiben.

    Btw: Die Bezeichner _X und _Y sind für die Implementation (Compiler und Standardbibliothek) reserviert, du darfst sie also nicht verwenden. Allgemeine Regel: Reserviert sind

    1. Bezeichner, die mit einem Unterstrich gefolgt von einem Großbuchstaben beginnen (_X, _Foo)
    2. Bezeichner, die zwei Unterstriche in Folge beinhalten (__foo, foo__bar)
    3. im globalen Scope alle weiteren Bezeichner, die mit einem Unterstrich beginnen (_foo, _bar).


  • 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