Ich kann nicht verstehen, warum wir einen Linker-Fehler haben, wenn wir eine statische Variable der üblichen (Nicht-Vorlagen-) Klasse im Header definieren, aber im Falle von Vorlagen funktioniert alles gut und außerdem haben wir eine einzelne Instanz der statischen Variablen unter allen Übersetzungseinheiten :
Sein Template-Header (template.h):
// template.h
template<typename T>
class Templ {
public:
static int templStatic;
};
template<typename T> Templ<T>::templStatic = 0;
Es ist die erste Einheit, die die Vorlage (unit1.cpp) verwendet.
// unit1.cpp
#include "template.h"
int method1() {
return Templ<void>::templStatic++;
}
Zweite Einheit hier (unit2.cpp):
// unit2.cpp
#include "template.h"
int method2() {
return Templ<void>::templStatic++;
}
Und schließlich main.cpp:
// main.cpp
#include <iostream>
int method1();
int method2();
int main(int argc, char** argv) {
std::cout << method1() << std::endl;
std::cout << method2() << std::endl;
}
Nach dem Kompilieren, Verknüpfen und Ausführen dieses Codes haben wir folgende Ausgabe:
0
1
Also, warum funktioniert im Falle von Vorlagen alles gut (und wie erwartet)? Wie Compiler oder Linker damit umgehen (wir können jede .cpp-Datei in einem separaten Aufruf des Compilers kompilieren und sie dann mit dem Linker verknüpfen, sodass Compiler und Linker nicht alle .cpp-Dateien gleichzeitig “sehen”)?
PS: Mein Compiler: msvcpp 9 (aber auch auf mingw geprüft)
Es wäre nützlicher, wenn Sie uns den Code zeigen würden nicht arbeiten.
– JesperE
12. Oktober 09 um 10:43 Uhr
Ich nehme an, dass Code, der nicht funktioniert, derjenige ist, bei dem Sie eine Variable in einem Header definieren, die in mehr als einer Datei enthalten ist (nicht extern), was zu einer Namenskollision führt.
– Falstro
12. Oktober 09 um 10:46 Uhr
Johannes Schaub – litb
Das liegt daran, dass die Definition des statischen Datenmembers selbst eine Vorlage ist. Das Zulassen ist aus dem gleichen Grund erforderlich, aus dem Sie eine Funktionsvorlage haben dürfen, die nicht mehrmals in einem Programm eingebettet ist. Sie benötigen die Vorlage, um die resultierende Entität (z. B. eine Funktion oder ein statisches Datenelement) zu generieren. Wenn es Ihnen nicht gestattet wäre, die Definition eines statischen Datenmembers zu setzen, wie würden Sie Folgendes instanziieren?
template<typename T>
struct F {
static int const value;
};
template<typename T>
int const F<T>::value = sizeof(T);
Es ist nicht bekannt, was T is – der Standard sagt, dass die Definition außerhalb der Klassenvorlage eine Vorlagendefinition ist, in der die Parameter vom Besitzer der Klassenvorlage geerbt werden.
Ich habe einige Experimente mit GCC gemacht. Im Folgenden haben wir eine implizite Instantiierung von F<float>::valueund eine explizite Spezialisierung von F<char>::value die in einer .cpp-Datei definiert werden muss, um keine doppelten Symbolfehler zu verursachen, wenn sie mehrmals enthalten sind.
// Translation Unit 1
template<typename T>
struct F {
static int value;
};
template<typename T>
int F<T>::value = sizeof(T);
// this would belong into a .cpp file
template<> int F<char>::value = 2;
// this implicitly instantiates F<float>::value
int test = F<float>::value;
int main() { }
Die zweite Übersetzungseinheit enthält nur eine weitere implizite Instantiierung desselben statischen Datenelements
template<typename T>
struct F {
static int value;
};
template<typename T>
int F<T>::value = sizeof(T);
int test1 = F<float>::value;
Hier ist, was wir mit GCC bekommen – es macht jede implizite Instanziierung zu einem schwachen Symbol und fügt es hier in einen eigenen Abschnitt ein. Schwache Symbole verursachen keine Fehler, wenn mehrere davon zur Verbindungszeit vorhanden sind. Stattdessen wählt der Linker eine Instanz aus und verwirft die anderen unter der Annahme, dass alle gleich sind
objdump -Ct main1.o # =>
# cut down to the important ones
00000000 l df *ABS* 00000000 main1.cpp
0000000a l F .text 0000001e __static_initialization_and_destruction_0(int, int)
00000000 l d .data._ZN1FIfE5valueE 00000000 .data._ZN1FIfE5valueE
00000028 l F .text 0000001c global constructors keyed to _ZN1FIcE5valueE
00000000 g O .data 00000004 F<char>::value
00000000 g O .bss 00000004 test
00000000 g F .text 0000000a main
00000000 w O .data._ZN1FIfE5valueE 00000004 F<float>::value
Also wie wir sehen können F<float>::value ist ein schwaches Symbol, was bedeutet, dass der Linker zur Linkzeit mehrere davon sehen kann. test, main und F<char>::value sind globale (nicht schwache) Symbole. Verknüpfung main1.o und main2.o zusammen sehen wir in der Kartenausgabe (-Wl,-M) die folgende
Dies zeigt an, dass tatsächlich alle außer einer Instanz gelöscht werden.
In Ordnung. Aber wie Linker, die zwei sehen “template Templ::templStatic = 0;” Definitionen (in unit1.cpp und unit2.cpp) behandeln diese Situation ? Besitzen Objektdateien einige C++-spezifische Metainformationen, die dem Linker mitteilen können, dass eine Definition ignoriert werden kann (und als Ergebnis haben wir keinen “mehrere Definitionen”-Linkerfehler)?
– cybevnm
12. Oktober 09 um 11:34 Uhr
Es gibt eine Lösung, Sie können eine übergeordnete Klasse erstellen und die statische Variable darin einfügen und dann Ihre Vorlagenklasse dazu bringen, sie privat zu erben, hier ist ein Beispiel:
class Parent
{
protected:
static long count;
};
long Parent::count = 0;
template<typename T>
class TemplateClass: private Parent
{
private:
int mKey;
public:
TemplateClass():mKey(count++){}
long getKey(){return mKey;}
}
int main()
{
TemplateClass<int> obj1;
TemplateClass<double> obj2;
std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;
return 0;
}
Ausgabe wird sein:
Object 1 key is: 0
Object 2 key is: 1
Sie sollten in Ihrer Antwort deutlich machen, dass alle TemplateClass-Instanzen die gleich statisch count. Insofern unterscheidet sich Ihre Antwort von der statischen Elementvariable mit Vorlagen in der akzeptierten Antwort: In diesem Fall wird das statische Element pro Vorlagenargument geteilt (dh jede Instanziierung der Vorlage mit demselben Vorlagentyp).
– Ichthyo
31. März ’18 um 23:30 Uhr
@Ichthyo das ist das erwartete Verhalten, also denke ich nicht, dass es klar sein muss?
– Papa
5. Januar 19 um 12:16 Uhr
Das liegt daran, dass Vorlagencode kein Quellcode ist; es sind Anweisungen zum Schreiben von Quellcode.
Die statische Nicht-Template-Variable ist tatsächlicher Quellcode, und der Compiler wird versuchen, genau das zu tun, was Sie sagen, indem er etwas zweimal einfügt. Daher müssen Sie die statische Variable in einer .cpp-Datei initialisieren und nur in der .h-Datei, die die Klasse beschreibt, darauf verweisen. Es entspricht einer globalen Variablen, die über extern deklariert wird.
Wenn der Compiler sieht
template<class T> Templ{...};
es macht nichts, außer eine Notiz zu machen, dass die Vorlage existiert. Soweit es betroffen ist, gibt es keinen Quellcode, der mit Templ verbunden ist. Das erste Mal, dass Sie sich tatsächlich darauf beziehen
Templ<int> Instance
Der Compiler sieht sich den gesamten Template<>-Code an, der mit Templ verknüpft ist, und verwendet ihn, um eine .h- und eine .cpp-Datei zu erstellen (die nur für die Dauer der Kompilierung vorhanden sind). Diese Dateien könnten so aussehen:
Temple_int.h
class Templ_int{
public:
static int templStatic;
};
Templ_int.cpp
#include "Templ_int.h"
Templ_int::templStatic = 0;
Und jeder
Templ<int>
wird ein Templ_int. Somit existiert der Quellcode zum Initialisieren der statischen Variablen nur einmal in einer vom Compiler erstellten .cpp-Datei. (Offensichtlich wäre die tatsächliche Compiler-spezifische Implementierung dieses Prozesses robust gegen das Erstellen einer Klasse mit einem ähnlichen Namen wie die Vorlage usw.)
Warum der Compiler einen Fehler auslöst, wenn wir die statischen Variablen nicht initialisieren. Ich habe versucht, die folgenden Zeilen zu entfernen Templ_int::templStatic = 0;
Es wäre nützlicher, wenn Sie uns den Code zeigen würden nicht arbeiten.
– JesperE
12. Oktober 09 um 10:43 Uhr
Ich nehme an, dass Code, der nicht funktioniert, derjenige ist, bei dem Sie eine Variable in einem Header definieren, die in mehr als einer Datei enthalten ist (nicht extern), was zu einer Namenskollision führt.
– Falstro
12. Oktober 09 um 10:46 Uhr