Nach einigen Wochen versuche ich, mein Wissen über Vorlagen mit dem Buch Vorlagen - Der vollständige Leitfaden von David Vandevoorde und Nicolai M. Josuttis zu erweitern und zu erweitern. Was ich derzeit zu verstehen versuche, ist die explizite Instanziierung von Vorlagen .
Ich habe eigentlich kein Problem mit dem Mechanismus als solchem, aber ich kann mir keine Situation vorstellen, in der ich diese Funktion verwenden möchte oder möchte. Wenn mir das jemand erklären kann, bin ich mehr als dankbar.
Wenn Sie eine Vorlagenklasse definieren, die nur für einige explizite Typen verwendet werden soll.
Fügen Sie die Vorlagendeklaration wie eine normale Klasse in die Header-Datei ein.
Fügen Sie die Vorlagendefinition wie eine normale Klasse in eine Quelldatei ein.
Instanziieren Sie dann am Ende der Quelldatei explizit nur die Version, die verfügbar sein soll.
Dummes Beispiel:
Quelle:
Main
quelle
Durch explizite Instanziierung können Kompilierungszeiten und Objektgrößen reduziert werden
Dies sind die Hauptgewinne, die es bieten kann. Sie stammen aus den folgenden zwei Effekten, die in den folgenden Abschnitten ausführlich beschrieben werden:
Entfernen Sie Definitionen aus den Headern
Durch explizite Instanziierung können Sie Definitionen in der CPP-Datei belassen.
Wenn sich die Definition im Header befindet und Sie sie ändern, kompiliert ein intelligentes Build-System alle Einschlüsse neu. Dies können Dutzende von Dateien sein, was die Kompilierung unerträglich langsam macht.
Das Einfügen von Definitionen in CPP-Dateien hat den Nachteil, dass externe Bibliotheken die Vorlage nicht mit ihren eigenen neuen Klassen wiederverwenden können. Die folgende Option enthält jedoch eine Problemumgehung.
Siehe konkrete Beispiele unten.
Objektneudefinition gewinnt: das Problem verstehen
Wenn Sie eine Vorlage in einer Header-Datei nur vollständig definieren, kompiliert jede einzelne Kompilierungseinheit, die diesen Header enthält, eine eigene implizite Kopie der Vorlage für jede andere verwendete Verwendung von Vorlagenargumenten.
Dies bedeutet viel nutzlose Festplattennutzung und Kompilierungszeit.
Hier ist ein konkretes Beispiel, in dem beide
main.cpp
und aufgrund ihrer Verwendung in diesen Dateiennotmain.cpp
implizit definiertMyTemplate<int>
werden.main.cpp
notmain.cpp
mytemplate.hpp
notmain.hpp
GitHub stromaufwärts .
Kompilieren und Anzeigen von Symbolen mit
nm
:Ausgabe:
Aus sehen
man nm
wir, dassW
dies ein schwaches Symbol bedeutet, das GCC gewählt hat, weil dies eine Vorlagenfunktion ist. Schwaches Symbol bedeutet, dass der kompilierte implizit generierte Code fürMyTemplate<int>
beide Dateien kompiliert wurde.Der Grund, warum es bei der Verknüpfung mit mehreren Definitionen nicht explodiert, ist, dass der Linker mehrere schwache Definitionen akzeptiert und nur eine davon auswählt, um sie in die endgültige ausführbare Datei einzufügen.
Die Zahlen in der Ausgabe bedeuten:
0000000000000000
: Adresse innerhalb des Abschnitts. Diese Null liegt daran, dass Vorlagen automatisch in einen eigenen Abschnitt eingefügt werden0000000000000017
: Größe des für sie generierten CodesWir können dies etwas deutlicher sehen mit:
was endet in:
und
_ZN10MyTemplateIiE1fEi
ist der verstümmelte Name, vonMyTemplate<int>::f(int)>
demc++filt
entschieden wurde, nicht zu entwirren.Wir sehen also, dass für jede einzelne Methodeninstanziierung ein separater Abschnitt generiert wird und dass jeder von ihnen natürlich Platz in den Objektdateien beansprucht.
Lösungen für das Problem der Objektneudefinition
Dieses Problem kann durch explizite Instanziierung vermieden werden.
Verschieben Sie die Definition in die CPP-Datei, lassen Sie nur die Deklaration in HPP, dh ändern Sie das ursprüngliche Beispiel wie folgt:
mytemplate.hpp
mytemplate.cpp
Nachteil: Externe Projekte können Ihre Vorlage nicht mit eigenen Typen verwenden. Außerdem müssen Sie alle Typen explizit instanziieren. Aber vielleicht ist dies ein Vorteil, da Programmierer es dann nicht vergessen werden.
Behalten Sie die Definition auf hpp bei und fügen Sie
extern template
jeden Einschluss hinzu, siehe auch: Verwenden einer externen Vorlage (C ++ 11), dh ändern Sie das ursprüngliche Beispiel wie folgt :mytemplate.cpp
main.cpp
notmain.cpp
Nachteil: Alle Einschließer müssen das
extern
zu ihren CPP-Dateien hinzufügen , was Programmierer wahrscheinlich vergessen werden.Behalten Sie die Definition auf hpp bei und fügen Sie
extern template
hpp für Typen hinzu, die explizit instanziiert werden sollen:mytemplate.hpp
mytemplate.cpp
main.cpp
notmain.cpp
Nachteil: Sie zwingen externe Projekte, ihre eigene explizite Instanziierung durchzuführen.
Mit einer dieser Lösungen
nm
enthält jetzt:so sehen wir haben haben nur
mytemplate.o
eine zusammenstellung vonMyTemplate<int>
wie gewünscht, währendnotmain.o
undmain.o
nicht weilU
bedeutet undefiniert.Entfernen Sie Definitionen aus den enthaltenen Headern, machen Sie aber auch Vorlagen für eine externe API verfügbar
Schließlich gibt es noch einen Anwendungsfall, den Sie berücksichtigen sollten, wenn Sie beides möchten:
Um dies zu lösen, können Sie eine der folgenden Aktionen ausführen:
mytemplate.hpp
: Vorlagendefinitionmytemplate_interface.hpp
: Vorlagendeklaration, die nur mit den Definitionen von übereinstimmtmytemplate_interface.hpp
, keine Definitionenmytemplate.cpp
:mytemplate.hpp
explizite Instantiierungen einschließen und vornehmenmain.cpp
und überall sonst in der Codebasis: einschließenmytemplate_interface.hpp
, nichtmytemplate.hpp
mytemplate.hpp
: Vorlagendefinitionmytemplate_implementation.hpp
: schließt jede Klasse einmytemplate.hpp
und fügtextern
sie hinzu , die instanziiert wirdmytemplate.cpp
:mytemplate.hpp
explizite Instantiierungen einschließen und vornehmenmain.cpp
und überall sonst in der Codebasis: einschließenmytemplate_implementation.hpp
, nichtmytemplate.hpp
Oder noch besser für mehrere Header: Erstellen Sie einen
intf
/impl
Ordner in Ihremincludes/
Ordner und verwenden Sie ihn immermytemplate.hpp
als Namen.Der
mytemplate_interface.hpp
Ansatz sieht folgendermaßen aus:mytemplate.hpp
mytemplate_interface.hpp
mytemplate.cpp
main.cpp
Kompilieren und ausführen:
Ausgabe:
Getestet in Ubuntu 18.04.
C ++ 20 Module
https://en.cppreference.com/w/cpp/language/modules
Ich denke, diese Funktion bietet das beste Setup für die Zukunft, sobald sie verfügbar ist, aber ich habe sie noch nicht überprüft, da sie auf meinem GCC 9.2.1 noch nicht verfügbar ist.
Sie müssen noch eine explizite Instanziierung durchführen, um die Geschwindigkeit zu erhöhen / die Festplatte zu speichern, aber zumindest haben wir eine vernünftige Lösung für "Entfernen von Definitionen aus enthaltenen Headern, aber auch Anzeigen einer externen API für Vorlagen", bei der das Kopieren nicht etwa 100 Mal erforderlich ist.
Die erwartete Verwendung (ohne die explizite Begründung, nicht sicher, wie die genaue Syntax aussehen wird, siehe: Wie wird die explizite Instanziierung von Vorlagen mit C ++ 20-Modulen verwendet? )
helloworld.cpp
main.cpp
und dann die unter https://quuxplusone.github.io/blog/2019/11/07/modular-hello-world/ erwähnte Zusammenstellung.
Daraus sehen wir, dass clang die Template-Schnittstelle + Implementierung in die Magie extrahieren kann
helloworld.pcm
, die eine LLVM-Zwischendarstellung der Quelle enthalten muss: Wie werden Vorlagen im C ++ - Modulsystem behandelt? Dies ermöglicht weiterhin die Vorlage von Vorlagenspezifikationen.So analysieren Sie Ihren Build schnell, um festzustellen, ob die Vorlageninstanziierung viel bringt
Sie haben also ein komplexes Projekt und möchten entscheiden, ob die Instanziierung von Vorlagen erhebliche Vorteile bringt, ohne den vollständigen Refactor auszuführen?
Die folgende Analyse kann Ihnen dabei helfen, die vielversprechendsten Objekte zu bestimmen oder zumindest auszuwählen, die beim Experimentieren zuerst umgestaltet werden sollen, indem Sie einige Ideen ausleihen: Meine C ++ - Objektdatei ist zu groß
Der Traum: ein Template-Compiler-Cache
Ich denke, die ultimative Lösung wäre, wenn wir bauen könnten mit:
und
myfile.o
würde dann zuvor zuvor kompilierte Vorlagen automatisch für mehrere Dateien wiederverwenden.Dies würde 0 zusätzlichen Aufwand für die Programmierer bedeuten, abgesehen davon, dass diese zusätzliche CLI-Option an Ihr Build-System übergeben wird.
quelle
Das hängt vom Compilermodell ab - anscheinend gibt es das Borland-Modell und das CFront-Modell. Und dann hängt es auch von Ihrer Absicht ab - wenn Sie eine Bibliothek schreiben, können Sie (wie oben erwähnt) die gewünschten Spezialisierungen explizit instanziieren.
Auf der GNU c ++ - Seite werden die Modelle hier erläutert: https://gcc.gnu.org/onlinedocs/gcc-4.5.2/gcc/Template-Instantiation.html .
quelle