Verwenden einer externen Vorlage (C ++ 11)

116

Abbildung 1: Funktionsvorlagen

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

Ist dies die richtige Verwendung extern templateoder verwende ich dieses Schlüsselwort nur für Klassenvorlagen wie in Abbildung 2?

Abbildung 2: Klassenvorlagen

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

Ich weiß, dass es gut ist, all dies in eine Header-Datei zu packen, aber wenn wir Vorlagen mit denselben Parametern in mehreren Dateien instanziieren, haben wir mehrere gleiche Definitionen und der Compiler entfernt sie alle (außer einer), um Fehler zu vermeiden. Wie benutze ich extern template? Können wir es nur für Klassen verwenden oder können wir es auch für Funktionen verwenden?

Außerdem können Abbildung 1 und Abbildung 2 zu einer Lösung erweitert werden, bei der sich Vorlagen in einer einzelnen Header-Datei befinden. In diesem Fall müssen wir das extern templateSchlüsselwort verwenden, um mehrere gleiche Instanziierungen zu vermeiden. Gilt das auch nur für Klassen oder Funktionen?

Codekiddy
quelle
3
Dies ist überhaupt nicht die richtige Verwendung von externen Vorlagen ... dies wird nicht einmal kompiliert
Dani
Könnten Sie sich etwas Zeit nehmen, um die (eine) Frage klarer zu formulieren? Wofür postest du den Code? Ich sehe keine Frage dazu. Auch extern template class foo<int>();scheint wie ein Fehler.
sehe
@Dani> es wird auf meinem Visual Studio 2010 einwandfrei kompiliert, mit Ausnahme der Warnmeldung: Warnung 1 Warnung C4231: Nicht standardmäßige Erweiterung verwendet: 'extern' vor expliziter Instanziierung der Vorlage
Codekiddy
2
Die Frage ist sehr einfach: Wie und wann wird das Schlüsselwort für externe Vorlagen verwendet? (externe Vorlage ist übrigens C ++ 0x new future) Sie sagten: "Auch die externe Vorlagenklasse foo <int> (); scheint ein Fehler zu sein." Nein, ist es nicht, ich habe ein neues C ++ - Buch und das ist ein Beispiel aus meinem Buch.
Codekiddy
1
@codekiddy: dann ist Visual Studio wirklich dumm. Im zweiten Fall stimmt der Prototyp nicht mit der Implementierung überein, und selbst wenn ich behebe, dass in der Nähe ()der externen Leitung "erwartete unqualifizierte ID" steht . Sowohl Ihr Buch als auch Ihr Visual Studio sind falsch. Versuchen Sie, einen standardkonformeren Compiler wie g ++ oder clang zu verwenden, und Sie werden das Problem sehen.
Dani

Antworten:

181

Sie sollten nur verwenden extern template, um den Compiler zu zwingen, eine Vorlage nicht zu instanziieren, wenn Sie wissen, dass sie an einer anderen Stelle instanziiert wird. Es wird verwendet, um die Kompilierungszeit und die Größe der Objektdatei zu reduzieren.

Beispielsweise:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

Dies führt zu folgenden Objektdateien:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

Wenn beide Dateien miteinander verknüpft sind, void ReallyBigFunction<int>()wird eine verworfen, was zu einer Verschwendung von Kompilierungszeit und Objektdateigröße führt.

Um Kompilierungszeit und Objektdateigröße nicht zu verschwenden, gibt es ein externSchlüsselwort, mit dem der Compiler keine Vorlagenfunktion kompiliert. Sie sollten dies genau dann verwenden, wenn Sie wissen, dass es in derselben Binärdatei an einer anderen Stelle verwendet wird.

Wechseln source2.cppzu:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

Führt zu folgenden Objektdateien:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

Wenn beide miteinander verknüpft werden, verwendet die zweite Objektdatei nur das Symbol aus der ersten Objektdatei. Keine Notwendigkeit zum Verwerfen und keine verschwendete Kompilierungszeit und Objektdateigröße.

Dies sollte nur innerhalb eines Projekts verwendet werden, wie in Zeiten, in denen Sie eine Vorlage vector<int>mehrmals verwenden, sollten Sie sie externin allen außer einer Quelldatei verwenden.

Dies gilt auch für Klassen und Funktionen als eine und sogar für Vorlagenelementfunktionen.

Dani
quelle
2
@codekiddy: Ich habe keine Ahnung, was Visual Studio damit meint. Sie sollten wirklich einen kompatibleren Compiler verwenden, wenn Sie möchten, dass der meiste C ++ 11-Code richtig funktioniert.
Dani
4
@Dani: Die beste Erklärung für externe Vorlagen, die ich bisher gelesen habe!
Pietro
90
"Wenn Sie wissen, dass es in derselben Binärdatei woanders verwendet wird." Das ist weder ausreichend noch erforderlich. Ihr Code ist "schlecht geformt, keine Diagnose erforderlich". Sie dürfen sich nicht auf eine implizite Instanziierung einer anderen TU verlassen (der Compiler darf diese wegoptimieren, ähnlich wie bei einer Inline-Funktion). Eine explizite Instanziierung muss in einer anderen TU bereitgestellt werden.
Johannes Schaub - litb
32
Ich möchte darauf hinweisen, dass diese Antwort wahrscheinlich falsch ist und ich davon gebissen wurde. Zum Glück hatte Johannes 'Kommentar eine Reihe von Stimmen und ich habe diesmal mehr darauf geachtet. Ich kann nur davon ausgehen, dass die überwiegende Mehrheit der Wähler in dieser Frage diese Art von Vorlagen nicht tatsächlich in mehreren Kompilierungseinheiten implementiert hat (wie ich es heute getan habe) ... Zumindest für Clang besteht der einzige todsichere Weg darin, diese Vorlagendefinitionen einzufügen dein Header! Sei gewarnt!
Steven Lu
6
@ JohannesSchaub-litb, könntest du etwas näher darauf eingehen oder vielleicht eine bessere Antwort geben? Ich bin mir nicht sicher, ob ich Ihre Einwände vollständig verstanden habe.
Andreee
48

Wikipedia hat die beste Beschreibung

In C ++ 03 muss der Compiler eine Vorlage instanziieren, wenn eine vollständig angegebene Vorlage in einer Übersetzungseinheit gefunden wird. Wenn die Vorlage in vielen Übersetzungseinheiten mit denselben Typen instanziiert wird, kann dies die Kompilierungszeiten erheblich verlängern. In C ++ 03 kann dies nicht verhindert werden. Daher wurden in C ++ 11 externe Vorlagendeklarationen eingeführt, die den externen Datendeklarationen entsprechen.

C ++ 03 hat diese Syntax, um den Compiler zu verpflichten, eine Vorlage zu instanziieren:

  template class std::vector<MyClass>;

C ++ 11 bietet jetzt folgende Syntax:

  extern template class std::vector<MyClass>;

Dies weist den Compiler an, die Vorlage in dieser Übersetzungseinheit nicht zu instanziieren.

Die Warnung: nonstandard extension used...

Microsoft VC ++ hatte bereits seit einigen Jahren eine nicht standardmäßige Version dieser Funktion (in C ++ 03). Der Compiler warnt davor, um Portabilitätsprobleme mit Code zu vermeiden, die auch auf verschiedenen Compilern kompiliert werden mussten.

Schauen Sie sich das Beispiel auf der verlinkten Seite an, um festzustellen, dass es ungefähr genauso funktioniert. Sie können davon ausgehen, dass die Nachricht bei zukünftigen Versionen von MSVC nicht mehr angezeigt wird, außer natürlich, wenn Sie gleichzeitig andere nicht standardmäßige Compiler-Erweiterungen verwenden.

sehe sehen
quelle
tnx für Ihre Antwort sehen, was dies bedeutet, ist, dass "externe Vorlage" Zukunft für VS 2010 vollständig funktioniert und wir die Warnung einfach ignorieren können? (Verwenden Sie Pragma, um beispielsweise die Nachricht zu ignorieren) und beachten Sie, dass die Vorlage in VSC ++ nicht mehr als pünktlich instanziiert wird. Compiler. Vielen Dank.
Codekiddy
4
"... was den Compiler anweist, die Vorlage in dieser Übersetzungseinheit nicht zu instanziieren ." Ich denke nicht, dass das wahr ist. Jede in der Klassendefinition definierte Methode zählt als Inline. Wenn also die STL-Implementierung Inline-Methoden für verwendet std::vector(ziemlich sicher, dass alle dies tun), externhat dies keine Auswirkung.
Andreas Haferburg
Ja, diese Antwort ist irreführend. MSFT-Dokument: "Das Schlüsselwort extern in der Spezialisierung gilt nur für Elementfunktionen, die außerhalb des Hauptteils der Klasse definiert sind. Funktionen, die innerhalb der Klassendeklaration definiert sind, gelten als Inline-Funktionen und werden immer instanziiert." Alle STL-Klassen in VS (zuletzt überprüft wurde 2017) haben leider nur Inline-Methoden.
0kcats
Das gilt für alle Inline-Deklarationen, unabhängig davon, wo sie entstehen, immer @ 0kcats
sehe
@sehe Der Verweis auf Wiki mit dem Beispiel std :: vector und ein Verweis auf MSVC in derselben Antwort lassen vermuten, dass die Verwendung von extern std :: vector in MSVC einen gewissen Nutzen haben könnte, obwohl es bisher keinen gibt. Nicht sicher, ob dies die Anforderung des Standards ist, möglicherweise haben andere Compiler das gleiche Problem.
0kcats
7

extern template wird nur benötigt, wenn die Vorlagendeklaration vollständig ist

Dies wurde in anderen Antworten angedeutet, aber ich denke, es wurde nicht genug Wert darauf gelegt.

Dies bedeutet, dass in den OP-Beispielen dies extern templatekeine Auswirkung hat, da die Vorlagendefinitionen in den Headern unvollständig waren:

  • void f();: nur Erklärung, kein Körper
  • class foo: deklariert Methode f(), hat aber keine Definition

Daher würde ich empfehlen, extern templatein diesem speziellen Fall nur die Definition zu entfernen : Sie müssen sie nur hinzufügen, wenn die Klassen vollständig definiert sind.

Beispielsweise:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

Kompilieren und Anzeigen von Symbolen mit nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

Ausgabe:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

und dann sehen man nmwir, dass Udies undefiniert bedeutet, so dass die Definition nur TemplCppwie gewünscht beibehalten wurde.

All dies läuft auf den Kompromiss vollständiger Header-Deklarationen hinaus:

  • Upsides:
    • ermöglicht externem Code die Verwendung unserer Vorlage mit neuen Typen
    • Wir haben die Möglichkeit, keine expliziten Instanziierungen hinzuzufügen, wenn das Aufblähen von Objekten in Ordnung ist
  • Nachteile:
    • Bei der Entwicklung dieser Klasse führen Änderungen an der Header-Implementierung dazu, dass Smart-Build-Systeme alle Includer neu erstellen. Dies können viele, viele Dateien sein
    • Wenn wir das Aufblähen von Objektdateien vermeiden möchten, müssen wir nicht nur explizite Instanziierungen durchführen (wie bei unvollständigen Header-Deklarationen), sondern auch extern templatejeden Einschluss hinzufügen , was Programmierer wahrscheinlich vergessen werden

Weitere Beispiele hierfür finden Sie unter: Explizite Vorlageninstanziierung - wann wird sie verwendet?

Da die Kompilierungszeit in großen Projekten so wichtig ist, würde ich unvollständige Vorlagendeklarationen dringend empfehlen, es sei denn, externe Parteien müssen Ihren Code unbedingt mit ihren eigenen komplexen benutzerdefinierten Klassen wiederverwenden.

In diesem Fall würde ich zunächst versuchen, Polymorphismus zu verwenden, um das Problem der Erstellungszeit zu vermeiden, und Vorlagen nur verwenden, wenn spürbare Leistungssteigerungen erzielt werden können.

Getestet in Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
4

Das bekannte Problem mit den Vorlagen ist das Aufblähen von Code, was die Folge der Generierung der Klassendefinition in jedem Modul ist, das die Spezialisierung auf Klassenvorlagen aufruft. Um dies zu verhindern, könnte man ab C ++ 0x das Schlüsselwort extern vor der Spezialisierung auf Klassenvorlagen verwenden

#include <MyClass>
extern template class CMyClass<int>;

Die explizite Instanziierung der Vorlagenklasse sollte nur in einer einzelnen Übersetzungseinheit erfolgen, vorzugsweise in der mit Vorlagendefinition (MyClass.cpp).

template class CMyClass<int>;
template class CMyClass<float>;
damirlj
quelle
0

Wenn Sie extern zuvor für Funktionen verwendet haben, wird für Vorlagen genau dieselbe Philosophie befolgt. Wenn nicht, kann es hilfreich sein, für einfache Funktionen nach außen zu gehen. Möglicherweise möchten Sie auch die externen Elemente in die Header-Datei einfügen und den Header bei Bedarf einfügen.

qqqqq
quelle