Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

BurningWave

Alter Hase

  • »BurningWave« ist der Autor dieses Themas

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

1

27.05.2010, 19:44

[Linker Fehler] Ungelöste externe - Was mache ich falsch?

Hallo,

ich bin gerade dabei, Lade- und Speicherfunktionen für meinen Soundeditor zu erstellen. Ich habe nun eine entsprechende Klasse mit Templates zum Schreiben beliebiger Datentypen in eine Datei erstellt. Diese lässt sich auch ohne Fehler kompilieren (logische Fehler schließe ich aber noch nicht aus), doch wenn ich eine write() Funktion von ihr aufrufe, bekomme ich eine seltsame Linkermeldung, mit der ich in diesem Zusammanheng nichts anfangen kann. Dass benötigte Dateien nicht eingebunden wurden, Schreibfehler,... kann ich auf jeden Fall ausschließen.

Die Header-Datei mit der Klasse zum Schreiben/Lesen in/von Daten:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//---------------------------------------------------------------------------

#ifndef FileH
#define FileH

class CFile
{
public:
    //Funktionen
    CFile()             {m_pFile = NULL;}
    CFile(FILE *pFile)  {m_pFile = pFile;}
    ~CFile()            {m_pFile = NULL;}

    CFile       operator= (FILE *pFile) {m_pFile = pFile; return *this;}

    void        write(AnsiString const &rsString);
    template <class T>
    void        write(T const &rValue);
    template <class T>
    void        write(list<T> const &rList);
    void        read(AnsiString *psString);
    template <class T>
    void        read(T *pValue);
    template <class T>
    void        read(list<T> *pList);

    //Exception-Classes
    class xFileError {
        protected:
            const type_info *m_pType_Info;
        public:
            xFileError() : m_pType_Info(NULL) {}
            const char* DataType() {
                if(m_pType_Info)
                    return m_pType_Info->name();
                else
                    return "Unbekannter Datentyp";
            }
    };
    class xWriteError : public xFileError {
        public:
            xWriteError() {m_pType_Info = NULL;}
            xWriteError(type_info &rType_Info) {m_pType_Info = &rType_Info;}
    };
    class xWriteErrorString : public xWriteError {
        public:
            xWriteErrorString() {m_pType_Info = &typeid(AnsiString);}
    };
    class xWriteErrorList : public xWriteError {
        public:
            template <class T>
            xWriteErrorList() {m_pType_Info = &typeid(list<T>);}
    };
    class xReadError : public xFileError {
        public:
            xReadError() {m_pType_Info = NULL;}
            xReadError(type_info &rType_Info) {m_pType_Info = &rType_Info;}
    };
    class xReadErrorString : public xReadError {
        public:
            xReadErrorString() {m_pType_Info = &typeid(AnsiString);}
    };
    class xReadErrorList : public xReadError {
        public:
            template <class T>
            xReadErrorList() {m_pType_Info = &typeid(list<T>);}
    };

private:
    //Zeiger auf eine Datei, auf die sich die write()- und read()-Funktionen beziehen
    FILE        *m_pFile;

    //interne Funktion zum Überprüfen, ob m_pFile ein gültiger Zeiger ist
    void        check() {assert(m_pFile);}
};

//---------------------------------------------------------------------------
#endif


Eine der write-Funktionen als Beispiel:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
template <class T>
void CFile::write(T const &rValue)
{
    check();

    if(!fwrite(&rValue, sizeof(T), 1, m_pFile))
        throw xWriteError(typeid(T));
}


Eine Funktion, in der die write()-Funktion aufgerufen wird:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Result CProject::WriteHeaderChunk()
{
    CFile File(m_pProjectFile);
    SChunkHeader tmpChunkHeader;
    tmpChunkHeader.Type = CT_HEADER_CHUNK;
    tmpChunkHeader.iSize = 0;

    try
    {
        File.write(tmpChunkHeader);

        File.write(m_HeaderChunk.iFileFormatVersion);
        File.write(m_HeaderChunk.iNumChunks);
        File.write(m_HeaderChunk.iYear);
        File.write(m_HeaderChunk.iTrackNumber);
        File.write(m_HeaderChunk.sProjectName);
        File.write(m_HeaderChunk.sTitle);
        File.write(m_HeaderChunk.sSubTitle);
        File.write(m_HeaderChunk.sAlbum);
        File.write(m_HeaderChunk.sInterpreters);
        File.write(m_HeaderChunk.sGenre);
        File.write(m_HeaderChunk.sCopyright);
        File.write(m_HeaderChunk.sComments);
    }
    catch(CFile::xWriteError &E) {
        char acTmp[256];
        sprintf(acTmp, "Exception xWriteError occurred:\nFehler beim Schreiben von \"%s\" in die Datei", E.DataType());
        return err(AnsiString(acTmp), __FILE__, __LINE__, ERR_FAILED, SDIAppForm->Handle);
    }
    catch(...) {
        return err("Exception ... occurred:\nUnbekannte Ausnahme", __FILE__, __LINE__, ERR_FAILED, SDIAppForm->Handle);
    }

    return ERR_OK;
}


Und noch die Compiler-Meldungen:

Quellcode

1
2
3
4
5
6
7
8
9
10
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<SChunkHeader>(const SChunkHeader&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<int>(const int&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<ECombineOperator>(const ECombineOperator&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<EChannels>(const EChannels&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<char>(const char&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<SInstrDefaultParams>(const SInstrDefaultParams&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<bool>(const bool&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<float>(const float&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<EParamMode>(const EParamMode&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ
[Linker Fehler] Error: Ungelöste externe 'void CFile::write<float>(const std::list<float, std::allocator<float> >&)' referenziert von C:\BORLAND-PROJEKTE\MUSIC MAKER\PROJECT.OBJ


Ich hoffe ihr könnt mir helfen, auch wenn ich das Borland Developer Studio benutze und bedanke mich schonmal jetzt für jede Hilfe, weil ich weiß, dass es etwas länger dauert, sich den ganze Code durchzusehen

mfg

marfi

Treue Seele

Beiträge: 100

Wohnort: Schwerte

  • Private Nachricht senden

2

27.05.2010, 19:49

Implementier mal die Funktionen im Header. Dann sollte es gehen.



Gruß

Marfi

GR-PA

Treue Seele

Beiträge: 326

Wohnort: Daheim

Beruf: Faulenzer

  • Private Nachricht senden

3

27.05.2010, 19:53

Templatefunktionen müssen innerhalb der Headerdatei definiert werden. (Da "export" von fast keinem Compiler unterstützt wird)
Signaturen werden überbewertet

BurningWave

Alter Hase

  • »BurningWave« ist der Autor dieses Themas

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

4

27.05.2010, 19:59

Soll ich einfach den kompletten Inhalt der .cpp Datei zwischen #endif und die Klassendekleration einfügen? Dann wäre meine .cpp Datei ja ganz leer?!

GR-PA

Treue Seele

Beiträge: 326

Wohnort: Daheim

Beruf: Faulenzer

  • Private Nachricht senden

5

27.05.2010, 20:34

Da wirst du wohl nicht drum herum kommen. Du könntest ja z.B versuchen, Aufgaben der template-Funktionen, die unabhängig von den template-Parametern sind, in Hilfsfunktionen auszulagern.
Signaturen werden überbewertet

BurningWave

Alter Hase

  • »BurningWave« ist der Autor dieses Themas

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

6

27.05.2010, 20:52

:D :D 8)

Ihr seid die Besten. Es hat funktioniert. Aber warum der Compiler meckert wenn Templates in .cpp-Dateien stehen ist mir unklar...

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

7

27.05.2010, 21:01

:D :D 8)

Ihr seid die Besten. Es hat funktioniert. Aber warum der Compiler meckert wenn Templates in .cpp-Dateien stehen ist mir unklar...


Da templates mitkompiliert werden müssen, damit die ganzen Typen ersetzt werden können, muss es immer komplett bekannt sein (im Gegensatz zu normalen Funktionen, wo lediglich die Signatur wichtig ist). Es gäbe theoretisch die Möglichkeit mit dem Schlüsselwort export das in eine Quelldatei zu verlagern, aber das funktioniert bei den meisten Compiler nicht.

8

29.05.2010, 19:50

Der Vorteil an Templates ist ja, dass du damit eine Art Interface definierst. Beispielsweise schreibst du die eine Template klasse für Vektoren, um damit Voktoren mit int, float und double Werten handhaben zu können. Dabei hast du den Vorteil, dass du die Klasse und die Funktionen für alle 3 Typen nur einmal schreiben musst.
Logischerweise brauche ein Vektor für doubles mehr Speicherplatz als einer für int und für float. Damit dein Programm aber weis, wann wieviel Speicher es reservieren muss, muss vorher bekannt sein, welche Vektor typen du überhaupt brauchst.
Der Große Vorteil dabei nennt sich "Metaprogrammierung". Benötigst du nur Vektoren für int, wird der Kompiler die Versionen für double und float garnicht erstellen. Es werden also quais nur die Templates übersetzt, die du auch wirklich benötigst (sprich instanzierst).

Damit das allerdings gut gehen kann, braucht der Kompiler mehr Informationen als für eine gewöhnliche Klasse. Zum Beispiel muss geprüft werden, dass alle Operationen, die du durchführst, auch wirklich möglich sind. So wird bei alle Templates, die du in deine Vektorklasse reinsteckst, auch überprüft, ob sie alle Funktionen besitzen, die du in der Vektorklasse forderst. Tun sie das nicht, knallts schon beim Kompilieren.
Und genau dafür braucht der Kompiler die genauen Angaben, was in der Funktion gemacht wird.

Soweit die hoffentlich verständliche Erklärung. :)

Was du allerdings machen kannst, um deine Klassendeklaration nicht so überladen aussehen zu lassen, ist folgendes:
Du schreibst in die Headerdatei ganz normal deine Deklaration. Zusätzlich erstellst du eine *.inl Datei (inline) und schreibst dort die Memberfunktionen hinein. Diese Datei musst du immer DIREKT nacht dem Header inkludieren. (Tipp aus Kalistas Buch, ich weis allerdings nicht, weiweit diese Methode Standardkonform ist)

Grüße,
Laguna
Portfolio runvs.io | Gamejolt | itch.io | PEWN | Twitter

9

30.05.2010, 21:46

Da der Typ eines Templates zum Zeitpunkt des kompilierens nicht bekannt ist, kann sich ein Template nicht in einer cpp befinden und muss vollständig im Header erstellt werden.

BurningWave

Alter Hase

  • »BurningWave« ist der Autor dieses Themas

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

10

30.05.2010, 23:55

Ich habe das Problem ja mittlerweile schon gelöst, aber mich würde noch interessieren, wie ich die Funktionen mit export deklarieren müsste, wenn das funktionieren würde?

Werbeanzeige