Template-Klasse: objektdatei + linker -> Fehler! ohne template gehts



  • Halli hallo miteinander,

    Bis jetzt bin ich mit dem Programmieren ganz gut vorangekommen. "einstieg in C++" von Arnold Willemer und "Die Programmiersprache C++" von Bjarne Stroustrup als Referenzbuch geholt, komme eigentlich ganz gut voran und kann die Bücher jedem empfehlen.

    Aber jetzt bin ich auf ein Problem mit Templates gestoßen. Ich glaube auch nicht so recht, dass es am Compiler liegt (Dev-C++) und poste deshalb hier ins Forum.

    Ich verwende ja die Objektdatei folgendermaßen:
    In klasse.hpp definiere ich die Klasse und in klasse.cpp, wird die header-Datei includiert und die Funktionen deklariert. Dann kompiliere ich das Projekt und es kommt eine obj-Datei dabei raus (oder o-Datei). Diese o-/obj-Datei mache ich dann dem Linker im Projekt, dass diese Klasse verwenden soll, bekannt und binde den Header klasse.hpp ein.

    Soweit zur Theorie...

    Beispiel ohne Template, das auch ohne Probleme funktioniert:
    (Falls ich mich nicht vertippt habe 😉 )

    //klasse.hpp
    
    #ifndef _KLASSE_HPP_
    #define _KLASSE_HPP_
    
    class klasse
    {
    public:
       void test_func();
    };
    
    #endif // _KLASSE_HPP_
    
    //klasse.cpp
    
    #include <iostream>
    using namespace std;
    #include "klasse.hpp"
    
    void klasse::test_func()
    {
       cout << "Ich bin eine Testfunktion!" << endl;
    }
    

    Das Compiliere ich jetzt, kriege dieo-/obj-Datei, mache sie Dem Linker im folgenden Projekt bekannt und binde klasse.hpp ein:

    // main.cpp
    
    #include <iostream>
    using namespace std;
    #include "klasse.hpp"
    
    int main()
    {
       klasse meine_klasse;
       meine_klasse.test_func();
    }
    

    Beispiel mit Template-Klasse, bei der der Linker nicht will:
    (Wahrscheinlich übersehe ich da einfach was...)

    //klasse.hpp
    
    #ifndef _KLASSE_HPP_
    #define _KLASSE_HPP_
    
    template <class T1, class T2> class klasse
    {
       void test_func();
    };
    
    #endif // _KLASSE_HPP_
    
    //klasse.cpp
    
    #include <iostream>
    using namespace std;
    #include "klasse.hpp"
    
    template <class T1, class T2> void klasse<T1,T2>::test_func()
    {
       cout << "Ich bin eine Testfunktion!" << endl;
    }
    

    Müsste sich auch wieder ganz normal zu einer o-/obj-Datei kompilieren lassen, wenn ich mich nicht vertippt habe...
    Dann mache ich diese wieder dem Linker im folgenden Projekt bekannt und inkludiere klasse.hpp.

    // main.cpp
    
    #include <iostream>
    using namespace std;
    #include "klasse.hpp"
    
    int main()
    {
       klasse meine_klasse<int,int>;
       meine_klasse.test_func();
    }
    

    Was ist daran falsch?
    Der Compiler wirft mir das an den Kopf:

    [Linker error] undefined reference to `klasse<int, int>::test_func()'
    

    (Hab die Meldung ein bisschen angepasst, da ich es mit namespace probiert hatte und de klassenname anders war, tut aber nichts zur Sache, denn ich habe das schlichtere Beispiel auch mal probiert und so sollte die Meldung aussehen)

    Wenn ich den Vorgang mit der Objektdatei weglasse und alles in eine Datei schreibe, dann geht es doch auch, warum aber nicht mit der Objektdatei?

    Ich habe geknobelt, komm aber nicht drauf.
    Ich habe es mir zwar abgewöhnt andere mit "Kleinigkeiten" und meinen Problemen zu nerven, aber ich komm einfach nicht mehr voran, möchte das jetzt gelöst haben und setzte nun auf euch...

    Bin jedem sehr dankbar für eine hilfreiche Antwort! Danke jetzt schon einmal für das Durchlesen^^
    MfG Knickedi



  • Hi!

    Bei Templates muss, bei den meisten Compilern, die Definition und Deklaration von der gleichen Compiliereinheit sichtbar sein. Meist werden Deklaration und Definition einfach in eine Datei gepackt (vorzugsweise Header ;)). Der Standard gibt zwar die Möglichkeit Templates zu exportieren, allerdings bieten das viele Compiler (die meisten) noch nicht an.

    Außerdem stimmt dein Code nich ganz:

    template <class T1, class T2> class klasse
    {
    public: // du willst test_func ja aufrufen
       void test_func();
    };
    
    // In der gleichen Datei definieren
    template <class T1, class T2> void klasse<T1,T2>::test_func()
    {
       std::cout << "Ich bin eine Testfunktion!" << std::endl;
    }
    
    int main()
    {
       //klasse meine_klasse<int,int>;
       klasse< int, int > meine_klasse; // so, nicht wie in der Zeile darüber
       meine_klasse.test_func();
    }
    

    grüße



  • Hoi.

    klasse meine_klasse<int,int>;
    

    muss heissen:

    klasse<int,int> meine_klasse;
    

    Außerdem hast du in der Template-Klasse das public vergessen:

    template <class T1, class T2> class klasse
    {
    public: // fehlt im Beispiel
       void test_func();
    };
    


  • David_pb schrieb:

    Bei Templates muss, bei den meisten Compilern, die Definition und Deklaration von der gleichen Compiliereinheit sichtbar sein. Meist werden Deklaration und Definition einfach in eine Datei gepackt (vorzugsweise Header ;)). Der Standard gibt zwar die Möglichkeit Templates zu exportieren, allerdings bieten das viele Compiler (die meisten) noch nicht an.

    Ok, alles klar. Damit wäre mir geholfen... Danke!

    schue schrieb:

    Hoi.

    klasse meine_klasse<int,int>;
    

    muss heissen:

    klasse<int,int> meine_klasse;
    

    Außerdem hast du in der Template-Klasse das public vergessen:

    template <class T1, class T2> class klasse
    {
    public: // fehlt im Beispiel
       void test_func();
    };
    

    Hmm... Flüchtigkeitsfehler. Hab den Eintrag schnell vor der Schule geschrieben.

    Ich danke euch beiden!
    Mit der Antwort von David_pb wäre ich soweit bedient (woher weiß man sowas?!)

    Vielen Danek für die schnelle Antwort.

    MfG Knickedi



  • Knickedi schrieb:

    ...(woher weiß man sowas?!)...

    Das steht z.Bsp. in der hiesigen FAQ



  • Knickedi schrieb:

    [(woher weiß man sowas?!)

    Sowas bekommt man im Laufe seiner Programmierzeit mit. Du weißt es jetzt ja auch! 😉


  • Mod

    Braunstein schrieb:

    Knickedi schrieb:

    ...(woher weiß man sowas?!)...

    Das steht z.Bsp. in der hiesigen FAQ

    Oder man überlegt es sich. Ein Funktionstemplate ist eben etwas völlig anderes als eine Templatefunktion. Traditionell und dem Konzept nach ist das Übersetzungsmodell so ausgelegt, dass Compiler bereits fertigen maschinenlesbaren Code erzeugt und der Linker lediglich noch externe Referenzen auflöst (die üblichweise den Code nur insoweit verändern, als dass noch ein paar Adressen 'eingepatched' werden müssen, was im Prinzip ein recht trivialer Vorgang ist). Für ein Funktionstemplate geht das aber schon im ersten Schritt schief - man kann ein Template nicht in maschinenlesbaren Code transformieren, das geht erst, wenn wir zusätzlich alle Argumente für das template kennen, und dann eine Templatefunktion haben. Diese Templatefunktion dagegen genügt dann wieder allen obigen Anforderungen und ist an sich eine ganz normale Funktion. Aber natürlich kann Compiler nicht spekulativ für alle möglichen Argumente im voraus spezialisieren und Code erzeugen - denn es gibt ja in der Regel sehr viele oder unendlich viele mögliche Argumente. Damit ist schon klar, dass obiger Code jedenfalls nach diesem einfachen Übersetzungsmodell nicht funktionieren kann. Der Sprachstandard sieht hier einmal das Schlüsselwort export vor. Das ist aber einerseits kaum implementiert und auch nicht ohne Probleme, andererseits ist auch klar, dass das, was da (in die Objektdatei) exportiert wird, doch etwas völlig anderes ist als gewöhnliche Funktionen. Andere Abhilfen ergeben sich aber auch zwingend logisch:
    - die Notwendigkeit, in main.cpp eine externe (also auf eine andere Übersetzungseinheit verweisende) Referenz zu benutzen, ergibt sich ja nur daraus, dass der Compiler in dieser Übersetzungseinheit nicht in der Lage ist, das Template zu spezialisieren. Und zwar einfach deshalb, weil er dort die Definition des Templates nicht kennt. Beseitigen wir letzteres entfällt auch ersteres (wie in David_pbs Beitrag)
    - die andere Möglichkeit setzt am Punkt des "spekulativen" Spezialisierens an. Der Compiler kann nicht wissen, welche Spezialisierungen in anderen Übersetzungseinheiten gebraucht werden und er kann hier auch keine Vorkehr treffen. Der Programmierer hat dieses Problem nicht: Durch explizite (oder implizite) Instanziierung in einer Übersetzungseinheit die die Templatedefinition kennt, werden diese Spezialisierungen (da es sich um normale Funktionen handelt) für alle Übersetzungseinheiten unmittelbar nutzbar. ein

    template klasse<int,int>;
    

    in klasse.cpp würde das Problem auch lösen.

    Man hat also prinzipiell 3 Möglichkeiten, das Problem zu lösen, wobei die Variante mit export - vor allem mangels Compilerunterstützung - keine praktische Bedeutung hat. Unter den anderen beiden Möglichkeiten wird man dort, wo es möglich ist, in der Regel die wählen, die die Templatedefinition überall sichtbar macht. Zwar wird mit beiden Verfahren das Prinzip der Kapselung im Grunde durchbrochen, der zweite Fall führt aber doch zu deutlich größeren organisatorischen Abhängigkeiten.



  • Danke für den sehr ausführlichen Beitrag.
    Wirklich mit Leib und Seele dabei...

    Also um das kurz und knapp zu fassen (damit ich das auch wirklich richtig verstehe):
    1. export vor die Deklaration im Header. Wird aber nicht von jedem Compiler unterstützt (wie ich auch gemerkt habe).
    2. "Alles" in einen Header packen und diesen inkludieren. Dann wird die Klasse aber jedes mal mitcompiliert.
    3. Der Programmierer spezialisiert sich in der cpp-Datei auf einen Typ des Templates, aber so kann er auch dann nur diesen verwenden.

    Ich hoffe, dass es richtig angekommen ist...

    Aber jetzt stellt sich mir eine Frage:
    Wenn ich keine Möglichkeit habe die Klasse als Template in einer Objektdatei auszulagern ohne mich zu spezialisieren oder alles immer neuzucompilieren, wie kriegt der Compiler das mit der STL hin?
    Schaff ich das auch? Oder sollte ich die Finger davon lassen?
    Nicht dass ich daran scheitere, weil ich die Klasse nicht getrennt compilieren kann, es ist reine Neugier...

    MfG Knickedi



  • Knickedi schrieb:

    1. export vor die Deklaration im Header. Wird aber nicht von jedem Compiler unterstützt

    Eigentlich von so gut wie keinem. AFAIK unterstützt zB der Comeau export.

    Knickedi schrieb:

    2. "Alles" in einen Header packen und diesen inkludieren. Dann wird die Klasse aber jedes mal mitcompiliert.

    Eigentlich übersetzt ein Compiler nur das, was auch wirklich instanziert wird. Es ist also nicht so, dass immer sämtliche Templates neu kompiliert werden. Wäre auch ziemlich ineffizient.

    Knickedi schrieb:

    Wenn ich keine Möglichkeit habe die Klasse als Template in einer Objektdatei auszulagern ohne mich zu spezialisieren oder alles immer neuzucompilieren, wie kriegt der Compiler das mit der STL hin?

    Die Implementation der STL befindet sich doch auch grösstenteils in Headern.



  • groovemaster schrieb:

    Die Implementation der STL befindet sich doch auch grösstenteils in Headern.

    Ich hatte mal irgendwo aufgeschnappt, dass in den Headern "nur" die Deklarationen stehen und Bibliotheken automatisch aufgrund von inkludiereten Dateien aus dem Library-Verzeichnis des Compilers dazugelinkt werden...

    Naja, will hier auch keinem weiter zur Last fallen und vom Thema abweichen.
    Meine Frage wurde ja eigentlich schon beantwortet.

    Danke nochmal!



  • Knickedi schrieb:

    Ich hatte mal irgendwo aufgeschnappt, dass in den Headern "nur" die Deklarationen stehen

    Was ja auch richtig ist. Aber aufgrund des fehlenden Supports von export bilden Templates hier eine Ausnahme.



  • camper schrieb:

    ...Ein Funktionstemplate ist eben etwas völlig anderes als eine Templatefunktion....

    Ist diese Nomenklatur eigentlich verbreitet/standardisiert ?
    Ich erinnere mich nur daran, dass man "Funktions-/Klassentemplate" sagen sollte für

    template <typename T> 
    void f(void);
    
    template <typename T> 
    class C;
    

    und nicht "Templatefunktion/-klasse" (um eben klarzustellen, dass es eine Schablone ist).
    Das, was der Compiler daraus macht, kenne ich nur als "Instanz" eines (Funktions-/Klassen-)Templates.
    Mir fällt allerdings gerade nicht ein, wo ich das gelesen haben (Sutter ? Vandevoorde/Josuttis ? ...)

    Gruß,

    Simon2.


  • Mod

    Simon2 schrieb:

    camper schrieb:

    ...Ein Funktionstemplate ist eben etwas völlig anderes als eine Templatefunktion....

    Ist diese Nomenklatur eigentlich verbreitet/standardisiert ?
    Ich erinnere mich nur daran, dass man "Funktions-/Klassentemplate" sagen sollte für

    template <typename T> 
    void f(void);
    
    template <typename T> 
    class C;
    

    und nicht "Templatefunktion/-klasse" (um eben klarzustellen, dass es eine Schablone ist).
    Das, was der Compiler daraus macht, kenne ich nur als "Instanz" eines (Funktions-/Klassen-)Templates.
    Mir fällt allerdings gerade nicht ein, wo ich das gelesen haben (Sutter ? Vandevoorde/Josuttis ? ...)

    Gruß,

    Simon2.

    Man könnte sagen, dass sie standardisiert ist, weil sie bereits in der Sprachdefinition angelegt ist. Die Verwirrung entsteht für den Anfänger vor allem dadurch, dass man (gerade am Anfang in einfachen Fällen ohne Spezialisierung) häufig einfach ein Funktionstemplate definiert, und anschließend ganz normale Funktionsaufrufe (ohne explizite Angabe der Argumente) durchführt. So entsteht natürlich leicht der Eindruck, dass das, was definiert wurde, bereits eine Funktion ist. Denn der Vorgang der Deduktion der Templateargumente und die Instantiierung für diese Argumente ist ja völlig automatisch und implizit. Außerdem gibt es ja beide Begriffe, die des Funktionstemplates als auch der Templatefunktion, die zudem in einem Zusammenhang stehen und daher auch häufig gemeinsam in einem Text auftauchen. Letzteres Problem betrifft dann auch Klassentemplates bzw. Templateklassen (als die konkreten Instanzen eines Klassentemplates). Im Grunde ist es ganz einfach: ein Template ist etwas, das noch durch Parameter näher bestimmt werden muss, und wir nennen es entweder Funktions- oder Klassentemplate, je nachdem, was durch diese Vorlage generiert werden soll, während eine Klasse bzw. Funktion eben gerade keine solchen (Template-)Parameter hat, und wir nennen das dann eine Templateklasse bzw. -funktion, um anzuzeigen, dass dieses Etwas auf ein Template zurückgeht. Wichtig wird diese Unterscheidung noch bei expliziten Templatespezialisierungen. Eine explizite Templatespezialisierung ist kein Template. Das hat wesentliche Auswirkungen auf die ODR. Ein Funktionstemplate muss (nach dieser Lösungsmöglichkeit aus den vorhergehenden Beiträgen) in jeder Übersetzungseinheit definiert werden, in der es gebraucht wird. Eine explizite Spezialisierung dieses Templates ist dagegen eine Funktion und hat damit linkage und unterliegt folglich der ODR (also nur eine Definition im Programm bei extern, es sei denn, wir machen sie inline).


Anmelden zum Antworten