Zitat aus der C ++ - Standardbibliothek: ein Tutorial und ein Handbuch :
Die einzige tragbare Möglichkeit, Vorlagen zu verwenden, besteht derzeit darin, sie mithilfe von Inline-Funktionen in Header-Dateien zu implementieren.
Warum ist das?
(Klarstellung: Header-Dateien sind nicht die einzige tragbare Lösung. Sie sind jedoch die bequemste tragbare Lösung.)
Antworten:
Vorsichtsmaßnahme: Es ist nicht erforderlich, die Implementierung in die Header-Datei aufzunehmen. Die alternative Lösung finden Sie am Ende dieser Antwort.
Der Grund, warum Ihr Code fehlschlägt, ist, dass der Compiler beim Instanziieren einer Vorlage eine neue Klasse mit dem angegebenen Vorlagenargument erstellt. Zum Beispiel:
Beim Lesen dieser Zeile erstellt der Compiler eine neue Klasse (nennen wir sie
FooInt
), die der folgenden entspricht:Folglich muss der Compiler Zugriff auf die Implementierung der Methoden haben, um sie mit dem Template-Argument (in diesem Fall
int
) zu instanziieren . Wenn diese Implementierungen nicht im Header enthalten wären, wären sie nicht zugänglich, und daher könnte der Compiler die Vorlage nicht instanziieren.Eine übliche Lösung hierfür besteht darin, die Vorlagendeklaration in eine Header-Datei zu schreiben, die Klasse dann in eine Implementierungsdatei (z. B. .tpp) zu implementieren und diese Implementierungsdatei am Ende des Headers einzufügen.
Foo.h
Foo.tpp
Auf diese Weise wird die Implementierung weiterhin von der Deklaration getrennt, ist jedoch für den Compiler zugänglich.
Alternative Lösung
Eine andere Lösung besteht darin, die Implementierung getrennt zu halten und alle benötigten Vorlageninstanzen explizit zu instanziieren:
Foo.h
Foo.cpp
Wenn meine Erklärung nicht klar genug ist, können Sie sich die C ++ Super-FAQ zu diesem Thema ansehen .
quelle
Viele richtige Antworten hier, aber ich wollte dies hinzufügen (der Vollständigkeit halber):
Wenn Sie am Ende der Implementierungs-CPP-Datei alle Typen, mit denen die Vorlage verwendet wird, explizit instanziieren, kann der Linker sie wie gewohnt finden.
Bearbeiten: Beispiel für eine explizite Vorlageninstanziierung hinzufügen. Wird verwendet, nachdem die Vorlage definiert und alle Elementfunktionen definiert wurden.
Dadurch werden die Klasse und alle ihre Mitgliedsfunktionen (nur) instanziiert (und somit dem Linker zur Verfügung gestellt). Eine ähnliche Syntax funktioniert für Vorlagenfunktionen. Wenn Sie also Überladungen von Nicht-Mitglied-Operatoren haben, müssen Sie möglicherweise dasselbe für diese tun.
Das obige Beispiel ist ziemlich nutzlos, da der Vektor in Headern vollständig definiert ist, außer wenn eine gemeinsame Include-Datei (vorkompilierter Header?) Verwendet wird
extern template class vector<int>
, um zu verhindern, dass er in allen anderen (1000?) Dateien, die Vektor verwenden , instanziiert wird.quelle
type
ohne sie manuell aufzulisten.vector
ist kein gutes Beispiel, da ein Container von Natur aus auf "alle" Typen abzielt. Es kommt jedoch sehr häufig vor, dass Sie Vorlagen erstellen, die nur für einen bestimmten Satz von Typen bestimmt sind, z. B. numerische Typen: int8_t, int16_t, int32_t, uint8_t, uint16_t usw. In diesem Fall ist es immer noch sinnvoll, eine Vorlage zu verwenden Es ist jedoch auch möglich und wird meiner Meinung nach empfohlen, sie explizit für die gesamte Gruppe von Typen zu instanziieren..cpp
Datei der Klasse eingefügt und die beiden Instanziierungen werden aus anderen.cpp
Dateien referenziert, und ich erhalte immer noch den Verknüpfungsfehler, dass die Mitglieder nicht gefunden werden.Dies liegt an der Notwendigkeit einer separaten Kompilierung und daran, dass Vorlagen ein Polymorphismus im Instanziierungsstil sind.
Gehen wir zur Erklärung etwas näher an den Beton heran. Angenommen, ich habe die folgenden Dateien:
class MyClass<T>
class MyClass<T>
MyClass<int>
Separate Kompilierung bedeutet, dass ich foo.cpp unabhängig von bar.cpp kompilieren kann . Der Compiler erledigt die harte Arbeit der Analyse, Optimierung und Codegenerierung auf jeder Kompilierungseinheit völlig unabhängig. Wir müssen keine Ganzprogrammanalyse durchführen. Es ist nur der Linker, der das gesamte Programm auf einmal bearbeiten muss, und die Arbeit des Linkers ist wesentlich einfacher.
bar.cpp muss nicht einmal existieren, wenn ich foo.cpp kompiliere , aber ich sollte trotzdem in der Lage sein, das foo.o zu verknüpfen, das ich bereits zusammen mit dem bar.o hatte. Ich habe gerade erst produziert, ohne foo neu kompilieren zu müssen .cpp . foo.cpp könnte sogar in eine dynamische Bibliothek kompiliert, ohne foo.cpp an einem anderen Ort verteilt und mit Code verknüpft werden, den sie Jahre nach dem Schreiben von foo.cpp schreiben .
"Polymorphismus im Instanziierungsstil" bedeutet, dass die Vorlage
MyClass<T>
keine generische Klasse ist, die zu Code kompiliert werden kann, der für jeden Wert von funktioniertT
. Das würde hinzufügen Overhead wie Boxen, um Funktionszeiger auf Verteilern und Konstrukteure weitergeben usw. Die Absicht von C ++ Vorlagen ist zu vermeiden , fast identisch zu schreibenclass MyClass_int
,class MyClass_float
etc, aber noch in der Lage sein , mit kompilierten Code , um am Ende das ist meist als ob wir hatten jede Version separat geschrieben. Eine Vorlage ist also buchstäblich eine Vorlage. Eine Klassenvorlage ist keine Klasse, sondern ein Rezept zum Erstellen einer neuen Klasse für jede Klasse, auf dieT
wir stoßen. Eine Vorlage kann nicht in Code kompiliert werden, sondern nur das Ergebnis der Instanziierung der Vorlage kann kompiliert werden.Wenn also foo.cpp kompiliert wird, kann der Compiler bar.cpp nicht sehen, um zu wissen, dass dies
MyClass<int>
erforderlich ist. Es kann die Vorlage sehenMyClass<T>
, aber es kann keinen Code dafür ausgeben (es ist eine Vorlage, keine Klasse). Und wenn bar.cpp kompiliert wird, kann der Compiler sehen, dass er eine erstellen mussMyClass<int>
, aber er kann die Vorlage nicht sehenMyClass<T>
(nur die Schnittstelle in foo.h ), sodass er sie nicht erstellen kann.Wenn foo.cpp selbst verwendet
MyClass<int>
, wird beim Kompilieren von foo.cpp Code dafür generiert. Wenn also bar.o mit foo.o verknüpft ist, können sie angeschlossen werden und funktionieren. Wir können diese Tatsache nutzen, um zu ermöglichen, dass ein endlicher Satz von Vorlageninstanziierungen in einer CPP-Datei implementiert wird, indem eine einzelne Vorlage geschrieben wird. Es gibt jedoch keine Möglichkeit für bar.cpp , die Vorlage als Vorlage zu verwenden und sie auf beliebigen Typen zu instanziieren. Es können nur bereits vorhandene Versionen der Vorlagenklasse verwendet werden, die der Autor von foo.cpp bereitgestellt hat .Sie könnten denken, dass der Compiler beim Kompilieren einer Vorlage "alle Versionen generieren" sollte, wobei diejenigen, die nie verwendet werden, während der Verknüpfung herausgefiltert werden. Abgesehen von dem enormen Overhead und den extremen Schwierigkeiten, mit denen ein solcher Ansatz konfrontiert wäre, weil "Typmodifikator" -Funktionen wie Zeiger und Arrays es sogar nur den eingebauten Typen ermöglichen, eine unendliche Anzahl von Typen hervorzurufen, was passiert, wenn ich jetzt mein Programm erweitere beim Hinzufügen:
class BazPrivate
und verwendetMyClass<BazPrivate>
Es gibt keine Möglichkeit, dass dies funktionieren könnte, wenn wir es auch nicht tun
MyClass<T>
MyClass<T>
, damit der CompilerMyClass<BazPrivate>
während der Kompilierung von baz.cpp generieren kann .Niemand mag (1), weil das Kompilieren von Kompilierungssystemen für die gesamte Programmanalyse ewig dauert und es unmöglich macht, kompilierte Bibliotheken ohne den Quellcode zu verteilen. Also haben wir stattdessen (2).
quelle
Vorlagen müssen vom Compiler instanziiert werden, bevor sie tatsächlich in Objektcode kompiliert werden. Diese Instanziierung kann nur erreicht werden, wenn die Vorlagenargumente bekannt sind. Stellen Sie sich nun ein Szenario vor, in dem eine Vorlagenfunktion deklariert
a.h
, definierta.cpp
und verwendet wirdb.cpp
. Wenna.cpp
kompiliert wird, ist nicht unbedingt bekannt, dass für die bevorstehende Kompilierungb.cpp
eine Instanz der Vorlage erforderlich ist, geschweige denn, welche spezifische Instanz dies wäre. Bei mehr Header- und Quelldateien kann die Situation schnell komplizierter werden.Man kann argumentieren, dass Compiler intelligenter gemacht werden können, um für alle Verwendungen der Vorlage nach vorne zu schauen, aber ich bin sicher, dass es nicht schwierig sein würde, rekursive oder auf andere Weise komplizierte Szenarien zu erstellen. AFAIK, Compiler machen keine solchen Vorausschau. Wie Anton betonte, unterstützen einige Compiler explizite Exportdeklarationen von Vorlageninstanziierungen, aber (noch?) Nicht alle Compiler unterstützen dies.
quelle
Eigentlich vor der C ++ 11 der Standard definierte das
export
Schlüsselwort , das wäre es möglich , in einer Header - Datei zu deklarieren Vorlagen machen und sie an anderer Stelle umzusetzen.Keiner der gängigen Compiler hat dieses Schlüsselwort implementiert. Das einzige, das ich kenne, ist das Frontend der Edison Design Group, das vom Comeau C ++ - Compiler verwendet wird. Bei allen anderen mussten Sie Vorlagen in Header-Dateien schreiben, da der Compiler die Vorlagendefinition für eine ordnungsgemäße Instanziierung benötigt (wie andere bereits betont haben).
Infolgedessen hat das ISO C ++ - Standardkomitee beschlossen, die
export
Funktion von Vorlagen mit C ++ 11 zu entfernen .quelle
export
tatsächlich gegeben hätte und was nicht ... und jetzt stimme ich den EDG-Leuten voll und ganz zu: Es hätte uns nicht gebracht, was die meisten Leute (ich selbst in '11) enthalten) denke, es würde, und der C ++ - Standard ist ohne besser dran.Obwohl Standard-C ++ keine solchen Anforderungen stellt, müssen bei einigen Compilern alle Funktions- und Klassenvorlagen in jeder verwendeten Übersetzungseinheit verfügbar sein. Tatsächlich müssen für diese Compiler die Hauptteile der Vorlagenfunktionen in einer Header-Datei verfügbar gemacht werden. Um es zu wiederholen: Das bedeutet, dass diese Compiler nicht zulassen, dass sie in Nicht-Header-Dateien wie CPP-Dateien definiert werden
Es gibt ein Export- Schlüsselwort, mit dem dieses Problem behoben werden soll, das jedoch bei weitem nicht portabel ist.
quelle
Vorlagen müssen in Headern verwendet werden, da der Compiler abhängig von den für Vorlagenparameter angegebenen / abgeleiteten Parametern unterschiedliche Versionen des Codes instanziieren muss. Denken Sie daran, dass eine Vorlage keinen Code direkt darstellt, sondern eine Vorlage für mehrere Versionen dieses Codes. Wenn Sie eine Nicht-Vorlagenfunktion in einer
.cpp
Datei kompilieren, kompilieren Sie eine konkrete Funktion / Klasse. Dies ist nicht der Fall für Vorlagen, die mit verschiedenen Typen instanziiert werden können, dh beim Ersetzen von Vorlagenparametern durch konkrete Typen muss konkreter Code ausgegeben werden.Es gab eine Funktion mit dem
export
Schlüsselwort, die für die separate Kompilierung verwendet werden sollte. Dieexport
Funktion ist inC++11
AFAIK veraltet und wurde nur von einem Compiler implementiert. Sie sollten nicht nutzenexport
. Eine getrennte Kompilierung ist inC++
oderC++11
aber möglicherweise nicht möglichC++17
, wenn Konzepte es schaffen, könnten wir eine Möglichkeit der getrennten Kompilierung haben.Damit eine separate Kompilierung erreicht werden kann, muss eine separate Überprüfung des Vorlagenkörpers möglich sein. Es scheint, dass eine Lösung mit Konzepten möglich ist. Schauen Sie sich dieses Papier an, das kürzlich auf der Sitzung des Normungsausschusses vorgestellt wurde. Ich denke, dies ist nicht die einzige Anforderung, da Sie noch Code für den Vorlagencode im Benutzercode instanziieren müssen.
Das separate Kompilierungsproblem für Vorlagen ist vermutlich auch ein Problem, das bei der Migration zu Modulen auftritt, an der derzeit gearbeitet wird.
quelle
Dies bedeutet, dass die portabelste Methode zum Definieren von Methodenimplementierungen von Vorlagenklassen darin besteht, sie innerhalb der Vorlagenklassendefinition zu definieren.
quelle
Obwohl es oben viele gute Erklärungen gibt, fehlt mir eine praktische Möglichkeit, Vorlagen in Header und Body zu unterteilen.
Mein Hauptanliegen ist es, eine Neukompilierung aller Vorlagenbenutzer zu vermeiden, wenn ich deren Definition ändere.
Alle Vorlageninstanziierungen im Vorlagenkörper zu haben, ist für mich keine praktikable Lösung, da der Vorlagenautor möglicherweise nicht alle weiß, ob seine Verwendung und der Vorlagenbenutzer möglicherweise nicht das Recht haben, sie zu ändern.
Ich habe den folgenden Ansatz gewählt, der auch für ältere Compiler funktioniert (gcc 4.3.4, aCC A.03.13).
Für jede Vorlagenverwendung gibt es ein typedef in einer eigenen Header-Datei (generiert aus dem UML-Modell). Sein Körper enthält die Instanziierung (die in einer Bibliothek endet, die am Ende verlinkt ist).
Jeder Benutzer der Vorlage enthält diese Header-Datei und verwendet das typedef.
Ein schematisches Beispiel:
MyTemplate.h:
MyTemplate.cpp:
MyInstantiatedTemplate.h:
MyInstantiatedTemplate.cpp:
main.cpp:
Auf diese Weise müssen nur die Vorlageninstanziierungen neu kompiliert werden, nicht alle Vorlagenbenutzer (und Abhängigkeiten).
quelle
MyInstantiatedTemplate.h
Datei und des hinzugefügtenMyInstantiatedTemplate
Typs. Es ist ein bisschen sauberer, wenn du das nicht benutzt, imho. Überprüfen Sie meine Antwort auf eine andere Frage, die dies zeigt: stackoverflow.com/a/41292751/4612476Nur um hier etwas Bemerkenswertes hinzuzufügen. Man kann Methoden einer Vorlagenklasse in der Implementierungsdatei gut definieren, wenn sie keine Funktionsvorlagen sind.
myQueue.hpp:
myQueue.cpp:
quelle
isEmpty
von keiner anderen Übersetzungseinheit außermyQueue.cpp
...Wenn das Problem die zusätzliche Kompilierungszeit und das Aufblähen der Binärgröße ist, die durch das Kompilieren der .h-Datei als Teil aller damit verwendeten .cpp-Module entstehen, können Sie in vielen Fällen die Template-Klasse von einer nicht-templatisierten Basisklasse für abstammen lassen Nicht typabhängige Teile der Schnittstelle und diese Basisklasse können in der CPP-Datei implementiert werden.
quelle
class XBase
wo immer ich ein implementieren musstemplate class X
, indem ich die typabhängigen TeileX
und den Rest einfügeXBase
.Das ist genau richtig, da der Compiler wissen muss, um welchen Typ es sich bei der Zuordnung handelt. Daher müssen Vorlagenklassen, Funktionen, Aufzählungen usw. auch in der Header-Datei implementiert werden, wenn sie öffentlich oder Teil einer Bibliothek (statisch oder dynamisch) sein sollen, da Header-Dateien im Gegensatz zu den c / cpp-Dateien NICHT kompiliert werden sind. Wenn der Compiler nicht weiß, dass der Typ nicht kompiliert werden kann. In .Net kann dies, weil alle Objekte von der Object-Klasse abgeleitet sind. Dies ist nicht .Net.
quelle
Der Compiler generiert Code für jede Vorlageninstanziierung, wenn Sie während des Kompilierungsschritts eine Vorlage verwenden. Beim Kompilieren und Verknüpfen werden CPP-Dateien in reinen Objekt- oder Maschinencode konvertiert, der Verweise oder undefinierte Symbole enthält, da die in Ihrer main.cpp enthaltenen .h-Dateien NOCH keine Implementierung haben. Diese können mit einer anderen Objektdatei verknüpft werden, die eine Implementierung für Ihre Vorlage definiert, sodass Sie über eine vollständige ausführbare Datei verfügen.
Da Vorlagen jedoch im Kompilierungsschritt verarbeitet werden müssen, um Code für jede von Ihnen definierte Vorlageninstanziierung zu generieren, funktioniert das einfache Kompilieren einer Vorlage, die von der Header-Datei getrennt ist, nicht, da sie aus genau diesem Grund immer Hand in Hand gehen dass jede Vorlageninstanziierung buchstäblich eine ganz neue Klasse ist. In einer regulären Klasse können Sie .h und .cpp trennen, da .h eine Blaupause dieser Klasse ist und .cpp die Rohimplementierung ist, sodass alle Implementierungsdateien regelmäßig kompiliert und verknüpft werden können. Die Verwendung von Vorlagen .h ist jedoch eine Blaupause dafür, wie Die Klasse sollte nicht so aussehen, wie das Objekt aussehen soll, was bedeutet, dass eine .cpp-Vorlagendatei keine reguläre Rohimplementierung einer Klasse ist, sondern lediglich eine Blaupause für eine Klasse. Daher kann jede Implementierung einer .h-Vorlagendatei '
Daher werden Vorlagen niemals separat kompiliert und nur dort kompiliert, wo Sie eine konkrete Instanziierung in einer anderen Quelldatei haben. Die konkrete Instanziierung muss jedoch die Implementierung der Vorlagendatei kennen, da einfach die geändert werden muss
typename T
Die Verwendung eines konkreten Typs in der .h-Datei wird den Job nicht erledigen, da die zu verknüpfende .cpp-Datei später nicht mehr gefunden werden kann, da die Erinnerungsvorlagen abstrakt sind und nicht kompiliert werden können. Daher bin ich gezwungen Um die Implementierung jetzt zu geben, damit ich weiß, was zu kompilieren und zu verknüpfen ist, und jetzt, wo ich die Implementierung habe, wird sie in die beiliegende Quelldatei verknüpft. Grundsätzlich muss ich in dem Moment, in dem ich eine Vorlage instanziiere, eine ganz neue Klasse erstellen, und das kann ich nicht, wenn ich nicht weiß, wie diese Klasse aussehen soll, wenn ich den von mir bereitgestellten Typ verwende, es sei denn, ich benachrichtige den Compiler von Die Vorlagenimplementierung, sodass der Compiler jetzt durchT
meinen Typ ersetzen und eine konkrete Klasse erstellen kann, die zum Kompilieren und Verknüpfen bereit ist.Zusammenfassend sind Vorlagen Blaupausen für das Aussehen von Klassen, Klassen Blaupausen für das Aussehen eines Objekts. Ich kann Vorlagen nicht getrennt von ihrer konkreten Instanziierung kompilieren, da der Compiler nur konkrete Typen kompiliert, dh Vorlagen, zumindest in C ++, sind reine Sprachabstraktion. Wir müssen Vorlagen sozusagen de-abstrahieren, und wir geben ihnen einen konkreten Typ, damit unsere Vorlagenabstraktion in eine reguläre Klassendatei umgewandelt und normal kompiliert werden kann. Das Trennen der .h-Datei der Vorlage und der .cpp-Datei der Vorlage ist bedeutungslos. Es ist unsinnig, weil die Trennung von .cpp und .h nur dort erfolgt, wo die .cpp einzeln kompiliert und einzeln mit Vorlagen verknüpft werden kann, da wir sie nicht separat kompilieren können, da Vorlagen eine Abstraktion sind.
Das heißt,
typename T
get wird während des Kompilierungsschritts ersetzt, nicht der Verknüpfungsschritt. Wenn ich also versuche, eine Vorlage zu kompilieren, ohneT
als konkreter Werttyp ersetzt zu werden, der für den Compiler völlig bedeutungslos ist und daher kein Objektcode erstellt werden kann, weil dies nicht der Fall ist weiß wasT
ist.Es ist technisch möglich, eine Art von Funktionalität zu erstellen, mit der die Datei template.cpp gespeichert und die Typen ausgetauscht werden, wenn sie in anderen Quellen gefunden werden. Ich denke, dass der Standard ein Schlüsselwort enthält
export
, mit dem Sie Vorlagen in eine separate Datei einfügen können cpp-Datei, aber nicht so viele Compiler implementieren dies tatsächlich.Nur eine Randnotiz: Wenn Sie Spezialisierungen für eine Vorlagenklasse vornehmen, können Sie den Header von der Implementierung trennen, da eine Spezialisierung per Definition bedeutet, dass ich mich auf einen konkreten Typ spezialisiere, der einzeln kompiliert und verknüpft werden kann.
quelle
Eine Möglichkeit zur getrennten Implementierung ist wie folgt.
inner_foo hat die Vorwärtsdeklarationen. foo.tpp hat die Implementierung und enthält inner_foo.h; und foo.h hat nur eine Zeile, um foo.tpp einzuschließen.
Bei der Kompilierung wird der Inhalt von foo.h nach foo.tpp kopiert und anschließend die gesamte Datei nach foo.h kopiert. Danach wird sie kompiliert. Auf diese Weise gibt es keine Einschränkungen und die Benennung ist im Austausch gegen eine zusätzliche Datei konsistent.
Ich mache das, weil statische Analysatoren für den Code brechen, wenn die Vorwärtsdeklarationen der Klasse in * .tpp nicht angezeigt werden. Dies ist ärgerlich, wenn Sie Code in eine IDE schreiben oder YouCompleteMe oder andere verwenden.
quelle
Ich schlage vor, auf diese gcc-Seite zu schauen, auf der die Kompromisse zwischen dem "cfront" - und dem "borland" -Modell für Vorlageninstanziierungen erläutert werden.
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
Das "Borland" -Modell entspricht dem, was der Autor vorschlägt, liefert die vollständige Vorlagendefinition und lässt die Dinge mehrmals kompilieren.
Es enthält explizite Empfehlungen zur Verwendung der manuellen und automatischen Vorlageninstanziierung. Mit der Option "-repo" können beispielsweise Vorlagen erfasst werden, die instanziiert werden müssen. Oder Sie deaktivieren die automatische Vorlageninstanziierung mithilfe von "-fno-implicit-templates", um die manuelle Vorlageninstanziierung zu erzwingen.
Nach meiner Erfahrung verlasse ich mich darauf, dass die Vorlagen C ++ Standard Library und Boost für jede Kompilierungseinheit instanziiert werden (unter Verwendung einer Vorlagenbibliothek). Für meine großen Vorlagenklassen führe ich einmal eine manuelle Vorlageninstanziierung für die von mir benötigten Typen durch.
Dies ist mein Ansatz, da ich ein Arbeitsprogramm und keine Vorlagenbibliothek zur Verwendung in anderen Programmen bereitstelle. Der Autor des Buches, Josuttis, arbeitet viel an Vorlagenbibliotheken.
Wenn ich mir wirklich Sorgen um die Geschwindigkeit machen würde, würde ich wahrscheinlich die Verwendung vorkompilierter Header https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html untersuchen
Das gewinnt in vielen Compilern an Unterstützung. Ich denke jedoch, dass vorkompilierte Header mit Template-Header-Dateien schwierig wären.
quelle
Ein weiterer Grund, warum es eine gute Idee ist, sowohl Deklarationen als auch Definitionen in Header-Dateien zu schreiben, ist die Lesbarkeit. Angenommen, es gibt eine solche Vorlagenfunktion in Utility.h:
Und in der Utility.cpp:
Dies erfordert, dass jede T-Klasse hier den Operator kleiner als (<) implementiert. Es wird ein Compilerfehler ausgegeben, wenn Sie zwei Klasseninstanzen vergleichen, die das "<" nicht implementiert haben.
Wenn Sie die Vorlagendeklaration und -definition trennen, können Sie daher nicht nur die Header-Datei lesen, um die Vor- und Nachteile dieser Vorlage anzuzeigen, um diese API für Ihre eigenen Klassen zu verwenden, obwohl der Compiler Ihnen dies mitteilt Fall, welcher Operator überschrieben werden muss.
quelle
Sie können Ihre Vorlagenklasse tatsächlich in einer .template-Datei anstatt in einer .cpp-Datei definieren. Wer sagt, dass man es nur in einer Header-Datei definieren kann, ist falsch. Dies funktioniert bis zu c ++ 98.
Vergessen Sie nicht, dass Ihr Compiler Ihre .template-Datei als C ++ - Datei behandelt, um den Intelli-Sinn zu bewahren.
Hier ist ein Beispiel dafür für eine dynamische Array-Klasse.
Jetzt definieren Sie in Ihrer .template-Datei Ihre Funktionen so, wie Sie es normalerweise tun würden.
quelle