Linker-problem mit C++ Templates--> woran liegts????



  • Hi,
    ich hab folgendes kleines Template-HelloWorld Program (siehe unten). In der .h-Datei hab ich die Deklaration und dann ganz normal in der cpp-datei die Methodeninhalte. ALLES OK SOWEIT, wenn ich aber in main() eine Instanz der Klasse erstelle (MyTemplate<int> foo;), dann gibt der LINKER eine Fehlermeldung zurueck, dass offensichtlich der Konstrukor und Destruktor nicht gefunden werden koennen....

    Was mach ich denn hier falsch???...

    Datei MyTemplateExample.h:
    **************************
    #pragma once
    /**A simple Template class with template methods*/
    template <class Type>
    class MyTemplateExample
    {
    public:
    //constructor
    MyTemplateExample(void);

    //destructor
    ~MyTemplateExample(void);

    //getter and setter
    Type getMember1(void);
    void setMember1(Type member);

    private:
    //a member variable
    Type member1;
    };

    Datei MyTemplateExample.cpp
    ***************************
    #include "StdAfx.h"
    #include ".\mytemplateexample.h"

    template <class Type>
    MyTemplateExample<Type>::MyTemplateExample(void)
    {
    }

    template <class Type>
    MyTemplateExample<Type>::~MyTemplateExample(void)
    {
    }

    template <class Type>
    Type MyTemplateExample<Type>::getMember1(void){
    return this->member1;
    }

    template <class Type>
    void MyTemplateExample<Type>::setMember1(Type member){
    this->member1 = member;
    }

    ====> Benutzung der Template-klasse --> Compiler hat kein Problem, aber Linker fliegt mir um die Ohren....warum?????

    int _tmain(int argc, _TCHAR* argv[])
    {
    MyTemplateExample<int> a;
    return 0;
    }

    HAT JEMAND NE IDEE, warum der Linker das nicht erlaubt?????...Wenn ich die Meldungen richtig deute, kann der Linker weder den Konstruktor noch den Destructor fuer die Klasse finden....

    BESTEN DANK?

    LINKER-OUTPUT:
    MyTemplatesExample error LNK2019: unresolved external symbol "public: __thiscall MyTemplateExample<int>::MyTemplateExample<int>(void)" (??0?$MyTemplateExample@H@@QAE@XZ) referenced in function _main



  • maxpower24 schrieb:

    HAT JEMAND NE IDEE, warum der Linker das nicht erlaubt?????...Wenn ich die Meldungen richtig deute, kann der Linker weder den Konstruktor noch den Destructor fuer die Klasse finden....

    Steht in unserer FAQ, stichwort "undefined reference"



  • Wo sind die CPP tags?

    Datei MyTemplateExample.h: 
    ************************** 
    #pragma once 
    /**A simple Template class with template methods*/ 
    template <class Type> 
    class MyTemplateExample 
    { 
    public: 
    //constructor 
    MyTemplateExample(void); 
    
    //destructor 
    ~MyTemplateExample(void); 
    
    //getter and setter 
    Type getMember1(void); 
    void setMember1(Type member); 
    
    private: 
    //a member variable 
    Type member1; 
    }; 
    
    Datei MyTemplateExample.cpp 
    *************************** 
    #include "StdAfx.h" 
    #include ".\mytemplateexample.h" 
    
    template <class Type> 
    MyTemplateExample<Type>::MyTemplateExample(void) 
    { 
    } 
    
    template <class Type> 
    MyTemplateExample<Type>::~MyTemplateExample(void) 
    { 
    } 
    
    template <class Type> 
    Type MyTemplateExample<Type>::getMember1(void){ 
    return this->member1; 
    } 
    
    template <class Type> 
    void MyTemplateExample<Type>::setMember1(Type member){ 
    this->member1 = member; 
    } 
    
    ====> Benutzung der Template-klasse --> Compiler hat kein Problem, aber Linker fliegt mir um die Ohren....warum????? 
    
    int _tmain(int argc, _TCHAR* argv[]) 
    { 
    MyTemplateExample<int> a; 
    return 0; 
    }
    

    Siehe: http://www.c-plusplus.net/forum/viewtopic-var-t-is-246875.html



  • Templatisierte Klassen können nicht ohne weiteres in Header und Implementierung aufgeteilt werden.

    Es gibt einen Ausweg, den allerdings nicht alle Compiler unterstützen. Darum lässt sich streiten ob das empfehlenswert ist oder nicht. Und zwar geht das mit dem "export" keyword, welches dem Compiler mitteilt, dass die Implementierung der Klassenmembern nicht im Header vorgenommen wird, sondern halt extern.

    Dazu definierst du deine Klasse wie folgt:

    export template <typename T>
    class MyClass
    {
    ...
    }
    

    Sag bitte mal bescheid ob das geht. Hab das auch noch nie getestet.

    Grüße

    Max



  • Max3000 schrieb:

    Es gibt einen Ausweg, den allerdings nicht alle Compiler unterstützen.

    Um genau zu sein, unterstützt es nur einer => schlechte Idee.

    s. auch

    Mögliche Workarounds:
    - alles direkt in die Klassendefinition einfügen; das wird bei großen Templates ziemlich unübersichtlich
    - am Ende der Header-Datei eine Implementationsdatei inkludieren, in der die Member definiert werden (üblicherweise hat diese die Endung .impl)
    - die gewünschten Template-Instanzen explizit definieren
    - das Schlüsselwort export benutzen; allerdings gibt es kaum Compiler, die dieses unterstützen

    aus den meistgestellten Fragen.



  • Michael E. schrieb:

    Max3000 schrieb:

    Es gibt einen Ausweg, den allerdings nicht alle Compiler unterstützen.

    Um genau zu sein, unterstützt es nur einer => schlechte Idee.

    Ehrlich? OK. Wieder was gelernt. Weißt du auch welcher? Ich war auf dem Stand dass sich da der C++ Standard spaltet und es viele gibt die das können. Hab aber nur ein recht altes Buch wo das so drin steht.

    Ich habe hier außerdem noch ein kleines Tutorial über Templates gefunden wo 3 verschiedene Workarounds beschrieben werden. Etwas runter scrollen, dann steht es ab Abschnitt 35.12:

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

    Vielleicht hilft es dir.
    Grüße

    Max



  • Max3000 schrieb:

    Michael E. schrieb:

    Max3000 schrieb:

    Es gibt einen Ausweg, den allerdings nicht alle Compiler unterstützen.

    Um genau zu sein, unterstützt es nur einer => schlechte Idee.

    Ehrlich? OK. Wieder was gelernt. Weißt du auch welcher?

    Naja, ich muss mich etwas korrekter ausdrücken. Es gibt AFAIK nur eine Implementierung, die vom Comeau und vom Borland gemeinsam verwendet wird.

    Export Templates werden übrigens vom neuen Standard nicht mehr unterstützt. Deshalb macht es noch weniger Sinn, diesen Weg zu empfehlen.



  • Einen sehr interessanten Bericht von den einzigen Menschen, die export jemals implementiert haben, findest du bei hier.



  • Hm ich bin nicht so firm mit den Feinheiten der C++ Normierungsversuche.

    Für ein anderes Forum hatte ich mal den Quelltext zu dem gleichen Problem (?) so nach Infos aus dem Netz ergänzt/umgesetzt - leider nicht sauber dokumentiert 😡
    ist schon ein paar Monate her.

    #ifndef ARRAY_H_
    #define ARRAY_H_
    
    #include <iostream>
    #include <stdexcept>
    
    using namespace std;
    
    template <class T, int n>
    class Array{
    private:
    	T wert[n];
    public:
    	int get_size() const;
    	T& operator [](int number);
    };
    
    template <class T, int n>
    T& Array<T, n>::operator [](int number){
    	if( number < 0 || number >= n){
    		throw out_of_range("Bereichsüberschreitung!");
    	}
    	return wert[number];
    }
    
    template <class T, int n>
    int Array<T, n>::get_size() const{
    	return n;
    }
    
    #endif
    

    die .cpp dazu

    #include "Array.h"
    using namespace std;
    
    template <class T, int n>
    T& Array<T, n>::operator [](int number){
    	if( number < 0 || number >= n){
    		throw out_of_range("Bereichsüberschreitung!");
    	}
    	return wert[number];
    }
    
    template <class T, int n>
    int Array<T, n>::get_size() const{
    	return n;
    }
    

    und die main:

    #include "Array.h"
    #include <conio.h>
    
    using namespace std;
    
    int main(){
    	Array<int, 5> ca1;
    	try{
    		for(int i = 0; i < 5; i++){
    			ca1[i] = i*i;
    			cout << ca1[i] << " ";
    		}
    	}
    	catch(out_of_range& e){
    		cout << e.what() << endl;
    	}
    	getch();
    }
    

    Da das anhand eines vorliegenden Quelltextes bearbeitet wurde, geht da wahrscheinlich noch was zu verbessern 🕶 Beispiel: C-Header 👍

    Kann mir einer das Foul erklären, wenn es in dem Quelltext das Thema betrifft?
    Oder ist das ein zulässiger Lösungsansatz?

    MfG f.-th.



  • Zu spät sollte erst lesen und dann schreiben 😡





  • Max3000 schrieb:

    Ich war auf dem Stand dass sich da der C++ Standard spaltet und es viele gibt die das können.

    Der Standard spaltet sich da nicht. Der Standard schreibt vor, dass es geht - und die meisten Compiler sind an der Stelle nicht standardkonform, weil sich gezeigt hat dass es keinen wirklichen Mehrwert im Vergleich zum Aufwand hat, export zu implementieren.



  • f.-th. schrieb:

    Kann mir einer das Foul erklären, wenn es in dem Quelltext das Thema betrifft?
    Oder ist das ein zulässiger Lösungsansatz?

    IMHO ist das gültiger Code, weil die Definitionen in der cpp-Datei exakt mit den Definitionen im Header übereinstimmen. Allerdings kannst du den Code in der einen Datei nicht ändern, ohne auch den Code in der anderen Datei zu ändern. Also hast du keinen Mehrwert von der cpp-Datei und kannst sie ganz löschen.



  • Michael E. schrieb:

    f.-th. schrieb:

    Kann mir einer das Foul erklären, wenn es in dem Quelltext das Thema betrifft?
    Oder ist das ein zulässiger Lösungsansatz?

    IMHO ist das gültiger Code, weil die Definitionen in der cpp-Datei exakt mit den Definitionen im Header übereinstimmen. Allerdings kannst du den Code in der einen Datei nicht ändern, ohne auch den Code in der anderen Datei zu ändern. Also hast du keinen Mehrwert von der cpp-Datei und kannst sie ganz löschen.

    Ich denke nicht, dass es gültiger Code ist, weil bereits zur Compilezeit in der ÜE von Array.cpp eine Neudefinition im Code steht. erlaubt wäre dagegen, wenn in zwei verschiedenen ÜEs die selbe Definition je einmal drinsteht, z.B. wenn man die Definitionen des op[] und von get_size() aus Array.h in die main.cpp verlagert. Dann hat man genau den Fall wo die Template-Ausnahme der ODR greift.

    Dass das so wie es ist ggf. vom Compiler geschluckt wird liegt wohl daran, dass das Template in der Array.cpp-ÜE nicht instantiiert wird. Eigentlich müsste der Compiler beim ersten Durchgang trotzdem feststellen dass er da doppelte Definitionen im Code hat. Die ODR-Ausnahme bezieht sich nämlich nicht auf Definitionen im Sourcecode innerhalb einer ÜE, sondern darauf, dass das selbe Symbol (d.h. Template-Instantiierung) zur Linkzeit in mehreren ÜEs vorhanden sein darf.



  • Hast wohl Recht, pumuckl. Hab nur leider meine Literatur nicht hier, ums zu verifizieren 😉



  • Wie machen es die Hersteller von Tools oder der STL, hab das jetzt nicht so geprüft, oder leben die mit der in meinem Link genannten Einschränkungen mit dem Explicit Instantiation Model auf int und long?
    Export lassen wir mal weg.
    Und dem Nutzer soll nicht der ganze Quelltext bekannt gegeben werden.

    Solche Fälle soll es ja geben.



  • f.-th. schrieb:

    Wie machen es die Hersteller von Tools oder der STL, hab das jetzt nicht so geprüft, oder leben die mit der in meinem Link genannten Einschränkungen mit dem Explicit Instantiation Model auf int und long?
    Export lassen wir mal weg.
    Und dem Nutzer soll nicht der ganze Quelltext bekannt gegeben werden.

    Wenn sie nicht den ganzen Quelltext bekannt geben wollen müssen sie sich wohl auf einzelne Typen beschränken. Vieles wird aber tatsächlich offen ausgeliefert => inclusion model. Man kann sich z.B: durchaus anschauen, wie die STL des jeweils genutzten Compilers implementiert ist, ist aber meistens keine große Freude weil da natürlich nicht auf Lesbarkeit geachtet wird...


Anmelden zum Antworten