709 Stimmen

Speichern von C++-Vorlagendefinitionen für Funktionen in einer .CPP-Datei

Ich habe einige Vorlagen-Code, der ich lieber in einer CPP-Datei gespeichert haben möchte, anstatt inline im Header. Ich weiß, dass dies möglich ist, solange man weiß, welche Vorlagentypen verwendet werden. Zum Beispiel:

.h Datei

class foo
{
public:
    template 
    void do(const T& t);
};

.cpp Datei

template 
void foo::do(const T& t)
{
    // Etwas mit t machen
}

template void foo::do(const int&);
template void foo::do(const std::string&);

Beachten Sie die letzten beiden Zeilen - die foo::do-Vorlagenfunktion wird nur mit ints und std::strings verwendet, so dass diese Definitionen bedeuten, dass die App verlinkt wird.

Meine Frage ist - ist das ein hässlicher Hack oder wird es mit anderen Compilern/Linkern funktionieren? Ich benutze diesen Code derzeit nur mit VS2008, möchte aber auf andere Umgebungen portieren.

30 Stimmen

Ich hatte keine Ahnung, dass dies möglich war - ein interessanter Trick! Es hätte bei einigen aktuellen Aufgaben erheblich geholfen, dies zu wissen - Prost!

107 Stimmen

Das Ding, das mich verwirrt, ist die Verwendung von do als Kennung :p

0 Stimmen

Ich habe etwas Ähnliches mit GCC gemacht, aber ich forsche immer noch.

9voto

Dies ist eine Standardmethode zur Definition von Vorlagenfunktionen. Ich glaube, es gibt drei Methoden, die ich zur Definition von Vorlagen gelesen habe. Oder vielleicht 4. Jede hat Vor- und Nachteile.

  1. Definieren Sie die Vorlage in der Klassendefinition. Ich mag das überhaupt nicht, weil ich denke, dass Klassendefinitionen streng zur Referenz sein sollten und einfach zu lesen sein sollten. Es ist jedoch viel weniger knifflig, Vorlagen in der Klasse als außerhalb zu definieren. Und nicht alle Vorlagendeklarationen befinden sich auf dem gleichen Komplexitätsniveau. Diese Methode macht die Vorlage auch zu einer echten Vorlage.

  2. Definieren Sie die Vorlage im selben Header, aber außerhalb der Klasse. Das ist meistens mein bevorzugter Weg. Es hält Ihre Klassendefinition ordentlich, die Vorlage bleibt eine echte Vorlage. Es erfordert jedoch vollständige Vorlagennamen, was knifflig sein kann. Auch ist Ihr Code für alle verfügbar. Aber wenn Sie möchten, dass Ihr Code inline ist, ist dies der einzige Weg. Sie können dies auch erreichen, indem Sie am Ende Ihrer Klassendefinitionen eine .INL-Datei erstellen.

  3. Fügen Sie den header.h und implementation.CPP in Ihre main.CPP ein. Ich denke, so wird es gemacht. Sie müssen keine Vorinstantiierungen vorbereiten, es wird sich wie eine echte Vorlage verhalten. Das Problem, das ich damit habe, ist, dass es nicht natürlich ist. Normalerweise fügen wir keine Quelldateien ein und erwarten auch keine. Ich nehme an, dass, da Sie die Quelldatei eingefügt haben, die Vorlagenfunktionen inline sein können.

  4. Diese letzte Methode, die der gepostete Weg war, definiert die Vorlagen in einer Quelldatei, genauso wie Nummer 3; aber anstatt die Quelldatei einzuschließen, instanziieren wir die Vorlagen vor, die wir benötigen werden. Ich habe kein Problem mit dieser Methode und sie ist manchmal nützlich. Wir haben einen großen Code, der nicht von Inline-Funktionen profitieren kann, also setzen Sie ihn einfach in eine CPP-Datei. Und wenn wir gängige Instanziierungen kennen und vordefinieren können. Dies spart uns im Grunde genommen das Schreiben derselben Dinge 5, 10 Mal. Diese Methode hat den Vorteil, unseren Code proprietär zu halten. Aber ich empfehle nicht, winzige, häufig verwendete Funktionen in CPP-Dateien zu platzieren. Da dies die Leistung Ihrer Bibliothek reduzieren wird.

Anmerkung: Ich kenne die Konsequenzen einer aufgeblähten Objektdatei nicht.

7voto

TarmoPikaro Punkte 4208

Wenn wir ein Beispiel betrachten, sagen wir für irgendeinen Grund, den Sie eine Template-Klasse haben wollen:

//test_template.h:
#pragma once
#include 

template 
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT::test()
{
    printf("int test (int)\n");
}

template <>
void DemoT::test()
{
    printf("int test (bool)\n");
}

Wenn Sie diesen Code mit Visual Studio kompilieren, funktioniert es direkt. gcc wird einen Linkerfehler produzieren (wenn dieselbe Header-Datei aus mehreren .cpp-Dateien verwendet wird):

Fehler: Mehrfache Definition von 'DemoT::test()'; your.o: .../test_template.h:16: bereits hier definiert

Es ist möglich, die Implementierung in eine .cpp-Datei zu verschieben, aber dann müssen Sie die Klasse so deklarieren -

//test_template.h:
#pragma once
#include 

template 
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT::test();

template <>
void DemoT::test();

// Instanziierung parametrisierter Template-Klassen, die Implementierung liegt auf der .cpp-Seite.
template class DemoT;
template class DemoT;

Und dann wird die .cpp-Datei so aussehen:

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT::test()
{
    printf("int test (int)\n");
}

template <>
void DemoT::test()
{
    printf("int test (bool)\n");
}

Ohne die letzten beiden Zeilen in der Header-Datei - funktioniert gcc problemlos, aber Visual Studio wird einen Fehler produzieren:

 Fehler LNK2019: nicht aufgelöstes externes Symbol "public: void __cdecl DemoT::test(void)" (?test@?$DemoT@H@@QEAAXXZ) in Funktion referenziert

Die Syntax der template class ist optional, falls Sie die Funktion über den .dll-Export freigeben wollen, aber das gilt nur für das Windows-System - daher könnte test_template.h so aussehen:

//test_template.h:
#pragma once
#include 

template 
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

#ifdef _WIN32
    #define DLL_EXPORT __declspec(dllexport) 
#else
    #define DLL_EXPORT
#endif

template <>
void DLL_EXPORT DemoT::test();

template <>
void DLL_EXPORT DemoT::test();

mit einer .cpp-Datei aus dem vorherigen Beispiel.

Dies verursacht jedoch mehr Kopfschmerzen für den Linker, also wird empfohlen, das vorherige Beispiel zu verwenden, wenn Sie keine .dll-Funktion exportieren.

0 Stimmen

Ausgezeichnete Antwort

5voto

Red XIII Punkte 5465

Dies ist definitiv kein hässlicher Hack, aber achten Sie darauf, dass Sie dies (die explizite Template-Spezialisierung) für jede Klasse/Typ machen müssen, den Sie mit dem gegebenen Template verwenden möchten. Im Falle von VIELEN Typen, die die Template-Instantiierung anfordern, kann es VIELE Zeilen in Ihrer .cpp-Datei geben. Um dieses Problem zu beheben, können Sie eine TemplateClassInst.cpp in jedem Projekt haben, das Sie verwenden, damit Sie mehr Kontrolle darüber haben, welche Typen instantiiert werden. Offensichtlich wird diese Lösung nicht perfekt sein (aka Allheilmittel), da Sie möglicherweise die ODR brechen :).

0 Stimmen

Sind Sie sicher, dass es ODR abbrechen wird? Wenn die Instanziierungslinien in TemplateClassInst.cpp auf die gleiche Quelldatei verweisen (die die Vorlagenfunktionsdefinitionen enthält), ist dann garantiert, dass der ODR nicht verletzt wird, da alle Definitionen identisch sind (auch wenn wiederholt)?

0 Stimmen

Bitte, was ist ODR?

0 Stimmen

@nonremovable: ODR steht für "One Definition Rule". Siehe hier für eine Erklärung.

4voto

Lou Franco Punkte 85315

Ja, das ist der Standardweg, um Spezialisierung explizit zu instanziieren. Wie du gesagt hast, kannst du dieses Template nicht mit anderen Typen instanziieren.

Bearbeitung: basierend auf Kommentar korrigiert.

0 Stimmen

Wenn es um die Terminologie geht, handelt es sich um eine "explizite Instanziierung".

4voto

Ben Collins Punkte 20125

In der neuesten Version gibt es ein Schlüsselwort (export), das dieses Problem möglicherweise lindert, aber es ist in keinem Compiler implementiert, von dem ich weiß, außer Comeau.

Siehe die FAQ-lite dazu.

3 Stimmen

AFAIK, der Export ist tot, weil sie immer wieder mit neuen Problemen konfrontiert werden, jedes Mal, wenn sie das letzte gelöst haben, was die Gesamtlösung immer komplizierter macht. Und das Schlüsselwort "export" ermöglicht es Ihnen sowieso nicht, aus einem CPP zu "exportieren" (immer noch von H. Sutter sowieso). Also sage ich: Halte nicht den Atem an...

2 Stimmen

Um den Export umzusetzen, benötigt der Compiler immer noch die vollständige Vorlagendefinition. Alles, was Sie gewinnen, ist, dass es in einer Art kompilierten Form vorliegt. Aber es besteht wirklich kein Grund dafür.

2 Stimmen

...und es ist verschwunden aus dem Standard, aufgrund einer übermäßigen Komplikation für minimalen Gewinn.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X