Ich habe einen Vorlagencode, den ich lieber in einer CPP-Datei als inline im Header gespeichert hätte. Ich weiß, dass dies möglich ist, solange Sie wissen, welche Vorlagentypen verwendet werden. Zum Beispiel:
.h Datei
class foo
{
public:
template <typename T>
void do(const T& t);
};
CPP-Datei
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
Beachten Sie die letzten beiden Zeilen - die Funktion foo :: do template wird nur mit ints und std :: strings verwendet. Diese Definitionen bedeuten also, dass die App verknüpft wird.
Meine Frage ist - ist das ein böser Hack oder funktioniert das mit anderen Compilern / Linkern? Ich verwende diesen Code derzeit nur mit VS2008, möchte ihn jedoch auf andere Umgebungen portieren.
do
als Kennung: ptemplate class foo<int>;template class foo<std::string>;
am Ende der CPP-Datei tun?Antworten:
Das von Ihnen beschriebene Problem kann durch Definieren der Vorlage in der Kopfzeile oder über den oben beschriebenen Ansatz gelöst werden.
Ich empfehle, die folgenden Punkte aus dem C ++ FAQ Lite zu lesen :
Sie gehen sehr detailliert auf diese (und andere) Vorlagenprobleme ein.
quelle
Für andere auf dieser Seite, die sich fragen, wie die richtige Syntax (wie ich) für die explizite Vorlagenspezialisierung (oder zumindest in VS2008) lautet, ist die folgende ...
In Ihrer .h-Datei ...
Und in Ihrer CPP-Datei
quelle
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:
quelle
a.cpp
(Definition der Funktiona() {}
) undb.cpp
(Definition der Funktionb() { a() }
), wird dies erfolgreich verknüpft. Wenn ich recht habe, dann scheint das obige Zitat nicht für den typischen Fall zu gelten ... irre ich mich irgendwo?inline
Funktioneninline
. Der Grund dafür ist, dass es ohne ein standardisiertes C ++ - ABI schwierig ist, den Effekt zu definieren, den dies sonst hätte.Dies sollte überall dort gut funktionieren, wo Vorlagen unterstützt werden. Die explizite Instanziierung von Vorlagen ist Teil des C ++ - Standards.
quelle
Ihr Beispiel ist korrekt, aber nicht sehr portabel. Es gibt auch eine etwas sauberere Syntax, die verwendet werden kann (wie von @ namespace-sid hervorgehoben).
Angenommen, die Vorlagenklasse ist Teil einer Bibliothek, die gemeinsam genutzt werden soll. Sollten andere Versionen der Vorlagenklasse kompiliert werden? Soll der Bibliotheksverwalter alle möglichen Vorlagenverwendungen der Klasse vorwegnehmen?
Ein alternativer Ansatz ist eine geringfügige Abweichung von dem, was Sie haben: Fügen Sie eine dritte Datei hinzu, die die Vorlagenimplementierungs- / Instanziierungsdatei ist.
foo.h Datei
foo.cpp Datei
foo-impl.cpp Datei
Die einzige Einschränkung ist, dass Sie dem Compiler mitteilen müssen, dass er kompilieren soll,
foo-impl.cpp
anstatt dassfoo.cpp
das Kompilieren des letzteren nichts bewirkt.Natürlich können Sie mehrere Implementierungen in der dritten Datei oder mehrere Implementierungsdateien für jeden Typ haben, den Sie verwenden möchten.
Dies ermöglicht viel mehr Flexibilität beim Freigeben der Vorlagenklasse für andere Zwecke.
Dieses Setup reduziert auch die Kompilierungszeiten für wiederverwendete Klassen, da Sie nicht in jeder Übersetzungseinheit dieselbe Headerdatei neu kompilieren.
quelle
foo.cpp
), aus denen Versionen tatsächlich kompiliert werden (infoo-impl.cpp
), und Deklarationen (infoo.h
). Ich mag es nicht, dass die meisten C ++ - Vorlagen vollständig in Header-Dateien definiert sind. Dies steht im Widerspruch zum C / C ++ - Standard von Paarenc[pp]/h
für jede Klasse / jeden Namespace / jede von Ihnen verwendete Gruppierung. Die Leute scheinen immer noch monolithische Header-Dateien zu verwenden, einfach weil diese Alternative nicht weit verbreitet oder bekannt ist.h/cpp
Paar behalte, obwohl ich die ursprüngliche Liste der Instanziierungen in einem Include-Guard umgeben musste, aber ich konnte das wie gewohnt kompilierenfoo.cpp
. Ich bin jedoch noch ziemlich neu in C ++ und würde mich interessieren, ob diese gemischte Verwendung zusätzliche Einschränkungen aufweist.foo.cpp
und zu entkoppelnfoo-impl.cpp
. Nicht#include "foo.cpp"
in derfoo-impl.cpp
Datei; Fügen Sie stattdessen die Deklaration hinzu,extern template class foo<int>;
umfoo.cpp
zu verhindern, dass der Compiler die Vorlage beim Kompilieren instanziiertfoo.cpp
. Stellen Sie sicher, dass das Build-System beide.cpp
Dateien erstellt und beide Objektdateien an den Linker übergibt. Dies hat mehrere Vorteile: a) Es ist klar,foo.cpp
dass es keine Instanziierung gibt; b) Änderungen an foo.cpp erfordern keine Neukompilierung von foo-impl.cpp.foo.cpp
infoo_impl.h
undfoo-impl.cpp
in justfoo.cpp
. Ich würde auch typedefs für instantiations von hinzufügenfoo.cpp
zufoo.h
, ebenfallsusing foo_int = foo<int>;
. Der Trick besteht darin, den Benutzern zwei Header-Schnittstellen zur Auswahl zu stellen. Wenn der Benutzer eine vordefinierte Instanziierung benötigt, schließt er einfoo.h
, wenn der Benutzer etwas benötigt, das nicht in der richtigen Reihenfolge istfoo_impl.h
.Dies ist definitiv kein böser Hack, aber beachten Sie, dass Sie dies (die explizite Vorlagenspezialisierung) für jede Klasse / jeden Typ tun müssen, den Sie mit der angegebenen Vorlage verwenden möchten. Bei VIELEN Typen, die eine Vorlageninstanziierung anfordern, kann Ihre CPP-Datei VIELE Zeilen enthalten. Um dieses Problem zu beheben, können Sie in jedem Projekt, das Sie verwenden, eine TemplateClassInst.cpp verwenden, damit Sie besser steuern können, welche Typen instanziiert werden. Offensichtlich ist diese Lösung nicht perfekt (auch bekannt als Silver Bullet), da Sie möglicherweise die ODR brechen :).
quelle
Im neuesten Standard gibt es ein Schlüsselwort (
export
), mit dem dieses Problem behoben werden kann. Es ist jedoch in keinem anderen mir bekannten Compiler als Comeau implementiert.Siehe dazu die FAQ-Lite .
quelle
Dies ist eine Standardmethode zum Definieren von Vorlagenfunktionen. Ich denke, es gibt drei Methoden, die ich zum Definieren von Vorlagen lese. Oder wahrscheinlich 4. Jeder mit Vor- und Nachteilen.
In Klassendefinition definieren. Ich mag das überhaupt nicht, weil ich denke, dass Klassendefinitionen nur als Referenz dienen und leicht zu lesen sein sollten. Es ist jedoch viel weniger schwierig, Vorlagen in der Klasse zu definieren als außerhalb. Und nicht alle Vorlagendeklarationen sind gleich komplex. Diese Methode macht die Vorlage auch zu einer echten Vorlage.
Definieren Sie die Vorlage im selben Header, jedoch außerhalb der Klasse. Dies ist meistens mein bevorzugter Weg. Es hält Ihre Klassendefinition aufgeräumt, die Vorlage bleibt eine echte Vorlage. Es erfordert jedoch eine vollständige Benennung der Vorlagen, was schwierig sein kann. Außerdem steht Ihr Code allen zur Verfügung. Wenn Sie jedoch 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.
Fügen Sie die Datei header.h und Implementation.CPP in Ihre main.CPP ein. Ich denke, so wird es gemacht. Sie müssen keine Vorinstanziierungen vorbereiten, es verhält sich wie eine echte Vorlage. Das Problem, das ich damit habe, ist, dass es nicht natürlich ist. Normalerweise schließen wir keine Quelldateien ein und erwarten diese auch nicht. Ich denke, da Sie die Quelldatei aufgenommen haben, können die Vorlagenfunktionen eingebunden werden.
Diese letzte Methode, wie sie veröffentlicht wurde, definiert die Vorlagen in einer Quelldatei, genau wie Nummer 3; Anstatt die Quelldatei einzuschließen, instanziieren wir die Vorlagen vorab auf diejenigen, die wir benötigen. Ich habe kein Problem mit dieser Methode und es ist manchmal nützlich. Wir haben einen großen Code, der nicht von Inline profitieren kann. Fügen Sie ihn einfach in eine CPP-Datei ein. Und wenn wir gemeinsame Instanziierungen kennen und sie vordefinieren können. Dies erspart uns, 5, 10 Mal im Grunde dasselbe zu schreiben. Diese Methode hat den Vorteil, dass unser Code geschützt bleibt. Ich empfehle jedoch nicht, winzige, regelmäßig verwendete Funktionen in CPP-Dateien einzufügen. Dies verringert die Leistung Ihrer Bibliothek.
Beachten Sie, dass mir die Folgen einer aufgeblähten obj-Datei nicht bekannt sind.
quelle
Ja, dies ist die Standardmethode für die explizite Instanziierung von
Spezialisierungen. Wie Sie angegeben haben, können Sie diese Vorlage nicht mit anderen Typen instanziieren.Bearbeiten: basierend auf Kommentar korrigiert.
quelle
Nehmen wir ein Beispiel, aus irgendeinem Grund möchten Sie eine Vorlagenklasse haben:
Wenn Sie diesen Code mit Visual Studio kompilieren, funktioniert er sofort. gcc erzeugt einen Linkerfehler (wenn dieselbe Header-Datei aus mehreren CPP-Dateien verwendet wird):
Es ist möglich, die Implementierung in eine CPP-Datei zu verschieben, aber dann müssen Sie die Klasse wie folgt deklarieren:
Und dann sieht .cpp so aus:
Ohne zwei letzte Zeilen in der Header-Datei funktioniert gcc einwandfrei, aber Visual Studio erzeugt einen Fehler:
Die Syntax der Vorlagenklasse ist optional, wenn Sie die Funktion über den DLL-Export verfügbar machen möchten. Dies gilt jedoch nur für die Windows-Plattform. Daher könnte test_template.h folgendermaßen aussehen:
mit .cpp-Datei aus dem vorherigen Beispiel.
Dies bereitet dem Linker jedoch mehr Kopfschmerzen. Es wird daher empfohlen, das vorherige Beispiel zu verwenden, wenn Sie die DLL-Funktion nicht exportieren.
quelle
Zeit für ein Update! Erstellen Sie eine Inline-Datei (.inl oder wahrscheinlich eine andere) und kopieren Sie einfach alle Ihre Definitionen darin. Stellen Sie sicher, dass Sie die Vorlage über jeder Funktion hinzufügen (
template <typename T, ...>
). Anstatt die Header-Datei in die Inline-Datei aufzunehmen, machen Sie jetzt das Gegenteil. Fügen Sie die Inline-Datei nach der Deklaration Ihrer Klasse ein (#include "file.inl"
).Ich weiß nicht wirklich, warum das niemand erwähnt hat. Ich sehe keine unmittelbaren Nachteile.
quelle
#include "file.inl"
, wird der Präprozessor den Inhalt vonfile.inl
direkt in den Header einfügen . Aus welchem Grund auch immer Sie die Implementierung im Header vermeiden wollten, diese Lösung löst dieses Problem nicht.template
Definitionen erforderlich sind . Ich verstehe, warum die Leute es tun wollen - um die größtmögliche Parität mit Nicht-Template-Deklarationen / -Definitionen zu erreichen, um die Schnittstellendeklaration sauber zu halten usw. -, aber es lohnt sich nicht immer. Es geht darum, die Kompromisse auf beiden Seiten zu bewerten und die am wenigsten schlechten auszuwählen . ... bis esnamespace class
zu einer Sache wird: O [ bitte sei eine Sache ]An dem Beispiel, das Sie gegeben haben, ist nichts auszusetzen. Aber ich muss sagen, ich glaube, es ist nicht effizient, Funktionsdefinitionen in einer CPP-Datei zu speichern. Ich verstehe nur die Notwendigkeit, die Deklaration und Definition der Funktion zu trennen.
In Verbindung mit der expliziten Klasseninstanziierung kann Ihnen die Boost Concept Check Library (BCCL) dabei helfen, Vorlagenfunktionscode in CPP-Dateien zu generieren.
quelle
Keiner der oben genannten Punkte hat für mich funktioniert. Hier ist, wie Sie ihn gelöst haben. Meine Klasse hat nur eine Methode als Vorlage.
.h
.cpp
Dadurch werden Linkerfehler vermieden und TemporaryFunction muss überhaupt nicht aufgerufen werden
quelle