Fehler beim Linken



  • Hallo,

    ich kriege folgende Fehler beim Compilieren:

    g++ -o ListTest List.h List.cpp main.cpp
    /tmp/ccgGN7aN.o: In function main': main.cpp:(.text+0x8b): undefined reference tostructure::List<int>::List()'
    main.cpp:(.text+0x9e): undefined reference to structure::List<int>::addElement(int)' main.cpp:(.text+0xb1): undefined reference tostructure::List<int>::addElement(int)'
    main.cpp:(.text+0xc4): undefined reference to structure::List<int>::addElement(int)' main.cpp:(.text+0xd7): undefined reference tostd::basic_ostream<char, std::char_traits<char> >& structure::operator<< <int>(std::basic_ostream<char, std::char_traits<char> >&, structure::List<int> const&)'
    main.cpp:(.text+0xf7): undefined reference to structure::List<int>::~List()' main.cpp:(.text+0x10d): undefined reference tostructure::List<int>::~List()'
    collect2: ld gab 1 als Ende-Status zurück

    Der Code:
    List.h

    #ifndef _LIST_H_
    #define _LIST_H_
    
    #include <ostream>
    
    using namespace std;
    
    namespace structure
    {
    
    template <typename T>
    class List
    {
    private:
            struct ListElement
            {
                    T data;
                    ListElement *next;
            };
            ListElement *head;
            ListElement *tail;
            int size;
    public:
            List();
            virtual ~List();
            void addElement(const T element);
            bool removeElement(const T element);
            int getSize() const;
            template <T>
            friend ostream& operator<<(ostream &o, const List<T> &list);
    };
    
    template <typename T>
    ostream& operator<<(ostream &o, const List<T> &list);
    
    };
    
    #endif /* LIST_H_ */
    

    List.cpp

    1 #include <cstdlib>
      2 #include "List.h"
      3
      4 namespace structure
      5 {
      6
      7 template <typename T>
      8 List<T>::List() : size(0), head(NULL), tail(NULL)
      9 {
     10 }
     11
     12 template <typename T>
     13 List<T>::~List()
     14 {
     15         ListElement *tmp;
     16         for (int i = 0; i < size; i++) {
     17                 tmp = head;
     18                 head = head->next;
     19                 delete tmp;
     20         }
     21         size = 0;
     22 }
     23
     24 template <typename T>
     25 void List<T>::addElement(const T element)
     26 {
     27         ListElement *newElement;
     28         newElement = new ListElement;
     29         newElement->data = element;
     30         newElement->next = NULL;
     31         tail->next = newElement;
     32         tail = newElement;
     33         size++;
     34 }
     35
     36 template <typename T>
     37 bool List<T>::removeElement(const T element)
     38 {
     39         return true;
     40 }
     41
     42 template <typename T>
     43 int List<T>::getSize() const
     44 {
     45         return size;
     46 }
    47
     48
     49 template <typename T>
     50 ostream& operator<<(ostream &o, const List<T> &list)
     51 {
     52         struct List<T>::ListElement *listElement;
     53
     54         listElement = list->head;
     55
     56         for (int i = 0; i < list->size; i++) {
     57                 o << listElement->data;
     58                 listElement = listElement->next;
     59         }
     60
     61         return o;
     62
     63 }
     64
     65 };
     66
    

    main.cpp :

    1 #include <iostream>
      2 #include <cstdlib>
      3 #include "List.h"
      4
      5 using namespace std;
      6 using namespace structure;
      7
      8 int main (int argc, char *argv[])
      9 {
     10         List<int> list;
     11
     12         list.addElement(1);
     13         list.addElement(2);
     14         list.addElement(5);
     15
     16         cout << list << endl;
     17
     18         return EXIT_SUCCESS;
     19 }
    

    Bitte um Hilfe.

    Gruß



  • [quote="HilfeSuchender_"]
    ich kriege folgende Fehler beim Compilieren:...
    [\quote]

    Von meiner Seite bekommst du auch noch weitere Fehler und Anmerkungen genannt, dazu aber gleich mehr.

    Grundsätzlich:
    Template komplett in den Header (inklusive der "Definition"), Templates sind nur Schablonen die immer ganz bekannt sein müssen. Die Templatedeklaration ist also im Gegensatz zu normalen Klassen nur inkl. der Ausgestalltung vollständig.
    Alternativ kannst du bei Header (aber sehr ungewöhlich) statt dessen die cpp inkludieren. Wenn man dies tut sollte man sie aber erkennbar machen (z.B. statt cpp tpl oder so nennen).

    Der Code:
    List.h

    #ifndef _LIST_H_
    #define _LIST_H_
    
    #include <ostream>
    
    using namespace std;
    

    NIEMALS ein using namespace in einen Header oder eine Templatedefinition schreiben! (Nachteile überwiegen hier die Vorteile)

    List();
        virtual ~List();
    

    Da du mit Zeigern und dynamisch allozierten Speicher arbeitest reicht dies nicht!

    Du musst auch entweder den Zuweisungsoperator und Kopierkonstruktor implementieren, oder die automatische Generierung verhindern. Davon abgesehen würde ich die Klasse nicht wie du mit virtual ~List() andeutest als Basisklasse zum ableiten vorsehen.

    void addElement(const T element);
    

    Bei unbekannten Typ wenn du nicht mit Typetraits arbeitest (wenn es dir nichts sagt stört es zu dem moment nicht) solltest du lieber mit const Referenzen arbeiten.

    List.cpp

    #include <cstdlib>
    

    Raus weg, pfui, aus... Zum einen verwendest du diese C-Lib nicht zum anderen solltest du lieber wenn bei C++ bleiben. Und wie gesagt gehört die Datei weg und bei Templates alles in den Header.

    List.cpp

    for (int i = 0; i < size; i++) {
    

    Ich weiß das ist jetzt Haarspalterei: Aber im Zweifel wenn es die Lesbarkeit nicht ändert immer ++i statt i++ verwenden. Bei integralen Datentypen macht das vielleicht noch nichts aus, aber bei Klassen kann dies gewaltige Unterschiede machen.
    ++i : Erhöht i direkt und gibt es dann zurück
    i++ : Erzeugt zuerst eine Kopie, erhöht dann i und gibt die Kopie zurück
    Bei Klassen sollte man unnötige Kopien vermeiden, wenn man es sich gleich angewöhnt macht man den fehler nicht.

    Wieso setzt du eigentlich am Ende des Destruktors noch size auf 0, die Variable existiert anschließend eh nicht mehr 😉

    main.cpp :

    #include <iostream>
    // Auch hier weg: #include <cstdlib> 
    #include "List.h"
    
    using namespace std; // Hier akzeptabel
    using namespace structure;
    
    // ich würde mit "int main()" arbeiten wenn ich die Argumente nicht brauche
    int main (int argc, char *argv[])
    {
        ...
        // Ist EXIT_SUCCESS im Standard definiert oder mal wieder VC-Gedöhns???
        // Habe ich in meinen 10 Jahren C++ noch nicht im Standard bemerkt
        // und verwende selbst return 0;
        return EXIT_SUCCESS;
    }
    

    cu André



  • Vielen Dank für die umfangreiche Hilfe 🙂

    asc schrieb:

    Template komplett in den Header (inklusive der "Definition"), Templates sind nur Schablonen die immer ganz bekannt sein müssen. Die Templatedeklaration ist also im Gegensatz zu normalen Klassen nur inkl. der Ausgestalltung vollständig.

    Vielen Dank, das hat geholfen.

    asc schrieb:

    NIEMALS ein using namespace in einen Header oder eine Templatedefinition schreiben! (Nachteile überwiegen hier die Vorteile)

    Soll ich das immer komplett (z.B. std::string) ausschreiben?

    asc schrieb:

    Du musst auch entweder den Zuweisungsoperator und Kopierkonstruktor implementieren, oder die automatische Generierung verhindern.

    Was meinst Du mit "automatische Generierung" verhindern?

    asc schrieb:

    Davon abgesehen würde ich die Klasse nicht wie du mit virtual ~List() andeutest als Basisklasse zum ableiten vorsehen.

    Ich habe irgendwo gelesen, dass man das als Faustregel immer machen sollte,
    da man nie weiß wer das ableitet.

    [/quote]

    asc schrieb:

    Bei unbekannten Typ [...] solltest du lieber mit const Referenzen arbeiten.

    So etwa: ??

    void addElement(const T &element);
    

    asc schrieb:

    // ich würde mit "int main()" arbeiten wenn ich die Argumente nicht brauche
    int main (int argc, char *argv[])
    {
    ...
    // Ist EXIT_SUCCESS im Standard definiert oder mal wieder VC-Gedöhns???
    // Habe ich in meinen 10 Jahren C++ noch nicht im Standard bemerkt
    // und verwende selbst return 0;
    return EXIT_SUCCESS;
    }
    [/cpp]

    EXIT_SUCCESS ist aus stdlib.h und wurde unter C (unter Linux) immer verwendet.

    Gruß 🙂

    cu André[/quote]



  • HilfeSuchender_ schrieb:

    asc schrieb:

    NIEMALS ein using namespace in einen Header oder eine Templatedefinition schreiben! (Nachteile überwiegen hier die Vorteile)

    Soll ich das immer komplett (z.B. std::string) ausschreiben?

    Ja, sollst du. Allerdings ist das wirklich nur im Header so notwendig.

    HilfeSuchender_ schrieb:

    asc schrieb:

    Du musst auch entweder den Zuweisungsoperator und Kopierkonstruktor implementieren, oder die automatische Generierung verhindern.

    Was meinst Du mit "automatische Generierung" verhindern?

    Wenn du die beiden nicht erstellst macht das der Compiler für dich und der macht nur flache Kopien. Das würde bei dir in die Hose gehen.

    HilfeSuchender_ schrieb:

    asc schrieb:

    Bei unbekannten Typ [...] solltest du lieber mit const Referenzen arbeiten.

    So etwa: ??

    void addElement(const T &element);
    

    Ja, genau so. Ein späterer Nutzer könnte ja hier eine dicke Klasse nehmen wollen, was ja zu unnötigen Kopieraufwand führen würde.

    Ciao



  • HilfeSuchender_ schrieb:

    friend ostream& operator<<(ostream &o, const List<T> &list);
    

    friend-Template direkt in der Klasse definieren, ansonsten bekommst du nen Linkerfehler nicht weg 😉

    -> http://www.parashift.com/c++-faq-lite/templates.html#faq-35.16



  • HilfeSuchender_ schrieb:

    asc schrieb:

    Du musst auch entweder den Zuweisungsoperator und Kopierkonstruktor implementieren, oder die automatische Generierung verhindern.

    Was meinst Du mit "automatische Generierung" verhindern?

    Wenn du schreibst:

    class A { };
    

    Generiert der Compiler daraus:

    class A {
        public:
            A();
            ~A();
            A(const & A);
            A& operator=(const & A);
    };
    

    Was bei Klassen ohne dynamische Speicherallozierung meist noch okay ist. Spätestens bei Zeigern und Speicherallozierung wird es aber problematisch. Hier solltest du entweder explizit den Zuweisungsoperator und Kopierkonstruktor verbieten (indem du ihn ohne Definition private deklarierst) oder diese komplett selbst definieren.

    Wenn du bei deinem Code folgendes machst:

    int main
    {
        List list1;
        // ... Liste füllen ...
        {
            List list2 = list1; // Hier kommt der Compilergenerierte Kopierkonstruktor
            // zum Einsatz der alle Elemente 1:1 kopiert. In deinem Fall
            // Zeiger
        }   // <-- Hier wird ~List() aufgerufen, und die Einträge gelöscht
            //     die aber nur in List1 alloziert wurden
        // <-- Hier sind die Elemente von list1 daher auch ungültig
        //     (Die Zeiger verweisen ja auf einen gelöschten Speicherbereich
    }   // <-- Hier sollte es knallen (Access Violation oder so) da die
        //     Speicherbereiche durch den Destruktor von list1 ja nochmal
        //     versucht werden zu löschen.
    

    Dies ist der Unterschied zwischen der flachen Kopie (Jedes Element wird 1:1 kopiert) und der tiefen Kopie (Man kopiert nur das 1:1 was möglich ist, für den Rest alloziert man z.B. neuen Speicher...).

    cu André



  • Vielen Dank für die zahlreichen Antworten 🙂
    Ihr habt mir sehr geholfen.


Anmelden zum Antworten