2009-10-12 21 views
48

Ich kann nicht verstehen, warum, wenn wir statische Variable der üblichen (Nicht-Vorlage) -Klasse im Header definieren, haben wir Linker Fehler, aber im Falle von Vorlagen funktioniert alles gut und außerdem werden wir Einzelinstanz von haben statische Variable bei allen Übersetzungseinheiten:Template statische Variable

Es ist 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 Vorlage (unit1.cpp)

// unit1.cpp 
#include "template.h" 

int method1() { 
    return Templ<void>::templStatic++; 
} 

Zweite Einheit mit ihm re (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 compilling, Verknüpfen und Ausführen dieses Codes, haben wir folgende Ausgabe:

0 
1 

Also, warum im Falle von Vorlagen funktioniert alles gut (und wie erwartet)? Wie Compiler oder Linker das behandeln (wir können jede CPP-Datei in getrennten Aufrufen des Compilers kompilieren und dann mit Caling zu Linker verknüpfen, damit Compiler und Linker nicht alle CPP-Dateien zur gleichen Zeit "sehen")?

PS: Mein Compiler: msvcpp 9 (aber auch auf mingw geprüft)

+0

Es wäre nützlicher, wenn Sie uns den Code zeigen würden, der ** nicht ** funktioniert. – JesperE

+0

Ich vermute, dass Code, der nicht funktioniert, derjenige ist, wo Sie eine Variable in einer Kopfzeile definieren, die in mehr als einer Datei enthalten ist (nicht extern), was zu einer Namenskollision führt. – falstro

Antwort

54

Es ist, weil die Definition des statischen Datenelement selbst ist eine Vorlage. Dies zuzulassen ist aus dem gleichen Grund notwendig, in dem Sie eine Funktionsvorlage haben dürfen, die nicht mehrfach in einem Programm enthalten ist. Sie benötigen die Vorlage, um die resultierende Entität (z. B. eine Funktion oder ein statisches Datenelement) zu generieren. Wenn Sie nicht erlaubt werden, würde die Definition eines statischen Datenelements zu setzen, wie würden Sie das folgende instanziiert

template<typename T> 
struct F { 
    static int const value; 
}; 

template<typename T> 
int const F<T>::value = sizeof(T); 

Es ist nicht bekannt, welche T ist - die Norm die Definition außerhalb der Klassenvorlage sagt eine Template-Definition ist , in dem die Parameter vom Klassenvorlagenbesitzer geerbt werden.


Ich habe ein paar Experimente mit GCC gemacht. Im Folgenden haben wir eine implizite Instanziierung von F<float>::value und eine explizite Spezialisierung von , die in einer CPP-Datei definiert werden muss, um doppelte Duplikatfehler nicht zu verursachen, wenn sie mehrfach 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 ein weitere implizite Instanziierung des gleichen statischen Daten Mitglieds

template<typename T> 
struct F { 
    static int value; 
}; 

template<typename T> 
int F<T>::value = sizeof(T); 

int test1 = F<float>::value; 

Hier ist das, was wir mit GCC bekommen - es jede implizite Instanziierung in ein schwaches Symbol macht und steckt sie in ein eigener Abschnitt hier. Schwache Symbole verursachen keine Fehler, wenn mehrere von ihnen zur Verbindungszeit vorhanden sind. Stattdessen wird der Linker eine Instanz wählen, und verwirft die anderen alle von ihnen sind die gleichen

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 

So wie wir sehen können, unter der Annahme, F<float>::value ein schwaches Symbol ist, das bedeutet, dass der Linker mehrere von diesen zur Verknüpfungszeit sehen können .test, main und sind globale (nicht schwache) Symbole. Die Verknüpfung main1.o und main2.o zusammen, sehen wir in der Karte ausgegeben (-Wl,-M) folgende

# (mangled name) 
.data._ZN1FIfE5valueE 
    0x080497ac  0x4 main1.o            
    0x080497ac    F<float>::value 

Dies zeigt, dass es alle außer einem Fall tatsächlich sinkt.

+0

Ok. Aber wie Linker, die zwei "Vorlage Templ :: templStatic = 0 sehen;" Definitionen (in unit1.cpp und unit2.cpp) behandeln diese Situation? Haben Objektdateien einige C++ - spezifische Metainformationen, die zum Linker sagen können, dass eine Definition ignoriert werden kann (und als Ergebnis haben wir nicht "mehrere Definitionen" Linkerfehler)? – cybevnm

+0

hat etwas GCC-Zeug hinzugefügt –

1

Es Lösung ist, können Sie eine übergeordnete Klasse erstellen und legen Sie die statische Variable drin, dann Template-Klasse es privat machen erben, hier 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; 
} 

Ausgang wird sein:

Object 1 key is: 0 
Object 2 key is: 1 
Verwandte Themen