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.

316voto

Aaron N. Tubbs Punkte 3603

Das von Ihnen beschriebene Problem kann durch die Definition der Vorlage im Header oder über den oben beschriebenen Ansatz gelöst werden.

Ich empfehle, die folgenden Punkte aus dem C++ FAQ Lite zu lesen:

Sie gehen ausführlich auf diese (und andere) Probleme mit Vorlagen ein.

51 Stimmen

Nur um die Antwort zu ergänzen, beantwortet der referenzierte Link die Frage positiv, d.h. es ist möglich, das zu tun, was Rob vorschlug, und den Code tragbar zu machen.

285 Stimmen

Können Sie einfach die relevanten Teile in der Antwort selbst posten? Warum ist eine solche Referenzierung überhaupt auf SO erlaubt? Ich habe keine Ahnung, wonach ich in diesem Link suchen soll, da er seitdem stark verändert wurde.

174voto

namespace sid Punkte 1676

Für andere auf dieser Seite, die sich fragen, was die richtige Syntax ist (so wie ich) für die explizite Template-Spezialisierung (oder zumindest in VS2008), ist es folgendes...

In Ihrer .h-Datei...

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

Und in Ihrer .cpp-Datei

template 
void foo::bar(const T &t)
{ }

// Explizite Vorlageninstantiierung
template class foo;

22 Stimmen

Meinst du "für explizite KLASSEN-Template-Spezialisierung"? In diesem Fall deckt das jede Funktion ab, die die vortemplatierte Klasse hat?

0 Stimmen

@Arthur scheint nicht, ich habe einige Vorlagemethoden im Header und die meisten anderen Methoden in CPP, funktioniert gut. Sehr schöne Lösung.

0 Stimmen

Im Fall des Fragestellers haben sie eine Funktionsvorlage, keine Klassenvorlage.

34voto

Cameron Tacklind Punkte 3466

Ihr Beispiel ist korrekt, aber nicht sehr portabel. Es gibt auch eine etwas sauberere Syntax, die verwendet werden kann (wie von @namespace-sid und anderen angemerkt).

Angenommen, die templatärisierte Klasse ist Teil einer Bibliothek, die gemeinsam genutzt werden soll...

Sollten andere Versionen der templatärisierten Klasse kompiliert werden?

Erwartet der Bibliotheksverwalter alle möglichen templatärisierten Verwendungen der Klasse vorab?

Ein alternativer Ansatz

Fügen Sie eine dritte Datei hinzu, die die Template-Implementierungs-/Instantiierungsdatei in Ihren Quellen darstellt.

lib/foo.hpp - aus der Bibliothek

#pragma once

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

lib/foo.cpp - das direkte Kompilieren dieser Datei verschwendet nur Kompilierzeit

// Hier Include-Guard einfügen, nur zur Sicherheit
#pragma once

#include "foo.hpp"

template 
void foo::bar(const T& arg) {
    // Irgendetwas mit `arg` machen
}

foo.MyType.cpp - bei Verwendung der Bibliothek, explizite Template-Instantiierung von foo

// In Betracht ziehen, einen "Anti-Guard" hinzuzufügen, um sicherzustellen, dass er nicht in anderen Übersetzungseinheiten enthalten ist
#if __INCLUDE_LEVEL__
  #error "Diese Datei nicht einfügen"
#endif

// Ja, wir fügen die .cpp-Datei ein
#include 
#include "MyType.hpp"

template class foo;

Organisieren Sie Ihre Implementierungen wie gewünscht:

  • Alle Implementierungen in einer Datei
  • Mehrere Implementierungsdateien, eine für jeden Typ
  • Eine Implementierungsdatei für jede Gruppe von Typen

Warum??

Dieses Setup sollte die Kompilierzeiten reduzieren, insbesondere für stark genutzten komplizierten templatärisierten Code, da Sie nicht die gleiche Headerdatei in jeder Übersetzungseinheit neu kompilieren. Es ermöglicht auch eine bessere Erkennung, welcher Code neu kompiliert werden muss, von Compilern und Build-Skripten, was die inkrementelle Build-Belastung verringert.

Beispielanwendungen

foo.MyType.hpp - muss über die öffentliche Schnittstelle von foo informiert sein, aber nicht über .cpp-Quellen

#pragma once

#include 
#include "MyType.hpp"

// `temp` deklarieren. Muss `foo.cpp` nicht einbeziehen
extern foo temp;

examples.cpp - kann auf lokale Deklaration verweisen, muss aber auch foo nicht neu kompilieren

#include "foo.MyType.hpp"

MyType instance;

// `temp` definieren. Muss `foo.cpp` nicht einbeziehen
foo temp;

void example_1() {
    // `temp` verwenden
    temp.bar(instance);
}

void example_2() {
    // Funktion lokale Instanz
    foo temp2;

    // Templatärisierte Bibliotheksfunktion verwenden
    temp2.bar(instance);
}

error.cpp - ein Beispiel, das mit reinen Header-Templates funktionieren würde, aber hier nicht funktioniert

#include 

// Verursacht Kompilierfehler zur Linkzeit, da wir nie die explizite Instantiierung hatten:
// template class foo;
// Der GCC-Linker gibt einen Fehler: "undefined reference to `foo::bar()'"
foo nonExplicitlyInstantiatedTemplate;
void linkerError() {
    nonExplicitlyInstantiatedTemplate.bar();
}

Anmerkung: Die meisten Compiler/Linter/Code-Helfer erkennen das nicht als Fehler, da gemäß dem C++-Standard kein Fehler vorliegt. Aber wenn Sie diese Übersetzungseinheit in eine vollständige ausführbare Datei einbinden, wird der Linker keine definierte Version von foo finden.


Alternativer Ansatz von: https://stackoverflow.com/a/495056/4612476

0 Stimmen

Was bringt dir das? Du musst immer noch foo-impl.cpp bearbeiten, um eine neue Spezialisierung hinzuzufügen.

3 Stimmen

Trennung von Implementierungsdetails (auch Definitionen in foo.cpp) von den tatsächlich kompilierten Versionen (in foo-impl.cpp) und Deklarationen (in foo.h). Ich mag es nicht, dass die meisten C++ -Vorlagen vollständig in Headerdateien definiert sind. Das steht im Widerspruch zum C/C++ -Standard von Paaren von c[pp]/h für jede Klasse/Namensraum/welche Gruppierung Sie auch immer verwenden. Die Leute scheinen immer noch monolithische Headerdateien zu verwenden, einfach weil diese Alternative nicht weit verbreitet oder bekannt ist.

1 Stimmen

@MK. Ich habe die expliziten Template-Instantiierungen zuerst am Ende der Definition in der Quelldatei platziert, bis ich weitere Instantiierungen an anderer Stelle benötigte (z.B. Modultests, die einen Mock als den typisierten Typ verwenden). Diese Trennung ermöglicht es mir, externe Instantiierungen hinzuzufügen. Darüber hinaus funktioniert es immer noch, wenn ich das Original als ein h/cpp-Paar belasse, obwohl ich die ursprüngliche Liste der Instantiierungen in einen Include-Guard einschließen musste, aber ich konnte den foo.cpp immer noch normal kompilieren. Ich bin jedoch immer noch ziemlich neu in C++ und würde gerne wissen, ob diese gemischte Verwendung zusätzliche Vorbehalte hat.

27voto

Konrad Rudolph Punkte 503837

Dieser Code ist wohlgeformt. Sie müssen nur darauf achten, dass die Definition der Vorlage zum Zeitpunkt der Instanziierung sichtbar ist. Um den Standard zu zitieren, § 14.7.2.4:

Die Definition einer nicht exportierten Funktionsschablone, einer nicht exportierten Elementfunktionsschablone oder eines nicht exportierten Elementfunktion oder statischen Datenelements einer Klassenschablone muss in jeder Übersetzungseinheit vorhanden sein, in der sie explizit instanziiert wird.

3 Stimmen

Was bedeutet nicht exportiert?

2 Stimmen

@Dan Nur innerhalb seiner Kompilationseinheit sichtbar, nicht außerhalb davon. Wenn Sie mehrere Kompilationseinheiten verknüpfen, können exportierte Symbole zwischen ihnen verwendet werden (und müssen eine einzige oder zumindest im Fall von Vorlagen konsistente Definitionen haben, da Sie sonst auf UB stoßen).

0 Stimmen

Danke. Ich dachte, dass alle Funktionen (standardmäßig) außerhalb der Kompilationseinheit sichtbar sind. Wenn ich zwei Kompilationseinheiten a.cpp (die die Funktion a() {} definiert) und b.cpp (die die Funktion b() { a() } definiert) habe, dann wird dies erfolgreich verknüpft. Wenn ich richtig liege, scheint das obige Zitat nicht auf den typischen Fall zuzutreffen... mache ich irgendwo einen Fehler?

17voto

moonshadow Punkte 81155

Dies sollte überall dort gut funktionieren, wo Vorlagen unterstützt werden. Die explizite Vorlageninstantiierung ist Teil des C++-Standards.

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