undefined reference bei Funktions-Templates und aktivierter Optimierung



  • Gegeben sei folgendes Minimalbeispiel:

    class1.h

    #ifndef CLASS1_H
    #define CLASS1_H
    #include <iostream>
    
    class Class1
    {
    public:
      template<typename T>
      void doSomething(const T& param);
    };
    
    #endif // CLASS1_H
    

    class1.cpp

    #include "class1.h"
    
    template<typename T>
    void Class1::doSomething(const T& param)
    {
      std::cout << param << std::endl;
    }
    
    void functionForExplicitInstantiation()
    {
      int i;
      Class1 c;
      c.doSomething(i);
    }
    

    main.cpp

    #include "class1.h"
    
    int main(int argc, char *argv[])
    {
      int i = 5;
      Class1 a;
      a.doSomething(i);
      return 0;
    }
    

    Aus diesem Code kann ich problemlos ein funktionierendes Executeable erstellen mit dem Befehl
    g++ class1.cpp main.cpp

    Sobald ich g++ jedoch sage, dass er/sie/es optimieren soll, gibt es einen Linkerfehler:
    g++ -O2 class1.cpp main.cpp
    /tmp/ccfPqCHd.o: In function main': main.cpp:(.text.startup+0x17): undefined reference tovoid Class1::doSomething<int>(int const&)'

    Im Prinzip ist mir klar, was hier passiert: Der Compiler kompiliert jede cpp-Datei einzeln und wirft dabei raus, was unnötig erscheint. In diesem Fall Class1::doSomething<int>(). Im Anschluss findet der Linker die Funktion nicht mehr in den Objektdateien und es kommt zu besagtem Fehler.

    Mir ist aber nicht klar, warum das passiert. Mit -O2 möchte ich dem Compiler sagen, er soll den gegebenen Code so kompilieren, dass dieser so schnell wie möglich ausgeführt werden kann. Ich sage nicht, dass irgendwelche Funktionen unnötig sind. Zwar ist ein kleines Executeable auch ganz nett, aber darum kann sich der Linker kümmern. Nach meinem Verständnis ist es Aufgabe des Linkers, allen benötigten Code (und nur den, denn ich will hier keine Library erstellen) aus den .o-Dateien zusammenzutragen und daraus ein Executeable zu machen.

    Warum verhält sich der Compiler dennoch so "besserwisserisch"? Gibt es einen Weg, das abzustellen ohne die Optimierung zu opfern? Vielen Dank schonmal für Antworten!


  • Mod

    Das Problem ist ein ganz anderes: Templatedefinitionen müssen bei Instanzierung verfügbar sein. Das heißt, die Funktionsdefinition muss in den Header. Keine Angst, bei Templates darf man auch mehrfache Definitionen im Programm haben, sofern diese gleich sind.

    Das wahre Wunder ist, dass der GCC offenbar in der Lage ist, die Definition auch über mehrere Dateien hinweg zu finden. Interessantes Feature, da muss ich mal tiefer graben, was es damit auf sich hat. Das darf er so machen, aber laut Sprachstandard muss dies nicht unterstützt werden. Und offenbar wird es nicht bei allen Compilereinstellungen unterstützt.

    PS: Die Option, die für den Linker"fehler" sorgt ist -finline-small-functions . Ich könnte jetzt spekulieren, warum das so ist, aber letztlich ist das müßig, da Templatedefinitionen, wie gesagt, sowieso bei Instanzierung vorliegen müssen und es dann auch funktioniert, egal mit welcher Optimierungsstufe.



  • Nur der Vollständigkeit halber: Man kann auch explizit Funktions Templates und Klassen instanzieren, ohne so eine Hilfsfunktion wo der Compiler eventuell Zeug wegoptimiert. Wenn du deine functionForExplicitInstantiation Funktion weglässt und stattdessen

    template void Class1::doSomething(const int& param);
    

    in die class1.cpp schreibst sollte es bei allen Optimierungsstufen funktionieren. Aber da Templates eigentlich in den Header gehören damit die Funktion auch nicht nur für int sondern alle Typen funktionieren soll ist sowas sehr unüblich.



  • Danke für die schnellen Antworten. Die Erklärung, dass doSomething<int>() ge-inline-t wird und die Methode daher nicht mehr gefunden wird, ergibt für mich Sinn.

    SeppJ schrieb:

    Das wahre Wunder ist, dass der GCC offenbar in der Lage ist, die Definition auch über mehrere Dateien hinweg zu finden.

    Finde ich ehrlich gesagt nicht so verwunderlich. Template hin oder her, es gibt einen gemeinsamen Header mit einer Deklaration. Der Name der Funktion ist in beiden cpp-Dateien eindeutig bestimmbar, nämlich Class1::doSomething<int>().

    Mit dem Tipp von sebi707 klappt es nun. Ist das nun ein g++-spezifischer Hack oder entspricht das dem C++-Standard?



  • Mr Train schrieb:

    Mit dem Tipp von sebi707 klappt es nun. Ist das nun ein g++-spezifischer Hack oder entspricht das dem C++-Standard?

    Das entspricht dem C++ Standard. Allerdings fallen mir kaum Gründe ein warum man dies tun sollte. Warum die Funktion nicht einfach mit const int& als Parameter deklarieren, wenn es die eh nur für int geben soll?


  • Mod

    Mr Train schrieb:

    SeppJ schrieb:

    Das wahre Wunder ist, dass der GCC offenbar in der Lage ist, die Definition auch über mehrere Dateien hinweg zu finden.

    Finde ich ehrlich gesagt nicht so verwunderlich. Template hin oder her, es gibt einen gemeinsamen Header mit einer Deklaration. Der Name der Funktion ist in beiden cpp-Dateien eindeutig bestimmbar, nämlich Class1::doSomething<int>().

    Stimmt, irgendwie bin ich selber durcheinander gekommen, welche Funktion wo aufgerufen wird. So macht das mit dem inline Sinn und es macht auch Sinn, warum die Funktion gefunden wird.



  • sebi707 schrieb:

    Allerdings fallen mir kaum Gründe ein warum man dies tun sollte. Warum die Funktion nicht einfach mit const int& als Parameter deklarieren, wenn es die eh nur für int geben soll?

    Der oben gepostete Code ist ein Minimalbeispiel, dessen einziger Zweck es ist, die selbe Fehlermeldung zu reproduzieren, die ich in einem realen Projekt bekomme. Ich kann hier ja nicht tausende Zeilen Code posten (und will den Code auch nicht preisgeben).

    Im realen Code gibt es natürlich auch noch weitere Template-Instanzen von Class1::doSomething() . Außerdem wird dort main.cpp oft geändert, class1.cpp jedoch selten. Durch die explizite Instantiierung in der class1.cpp Datei erhalte ich eine kürzere Compilezeit, als wenn ich die Methodendefinition mit in den Header stecken würde.


Anmelden zum Antworten