2009-10-12 21 views
48

Nie mogę zrozumieć, dlaczego jeśli zdefiniujemy zmienną statyczną zwykłej (nie-szablonowej) klasy w nagłówku, mamy błąd linkera, ale w przypadku szablonów wszystko działa dobrze, a ponadto będziemy mieć pojedyncze wystąpienie zmienna statyczna wśród wszystkich jednostek tłumaczeniowych:Zmienna statyczna szablonu

To nagłówek szablonu (template.h):

// template.h 
template<typename T> 
class Templ { 
public: 
    static int templStatic; 
}; 

template<typename T> Templ<T>::templStatic = 0; 

To pierwsza jednostka przy użyciu szablonu (unit1.cpp)

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

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

Druga jednostka on re (unit2.cpp):

// unit2.cpp 
#include "template.h" 
int method2() { 
    return Templ<void>::templStatic++; 
} 

I wreszcie 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; 
} 

Po compilling, łączenie i wykonaniu tego kodu, będziemy mieli następujące dane wyjściowe:

0 
1 

Dlaczego w przypadku szablonów wszystko działa poprawnie (i zgodnie z oczekiwaniami)? Jak radzi sobie z tym kompilator lub linker (możemy skompilować każdy plik .cpp w oddzielnym wywołaniu kompilatora, a następnie połączyć je z calingiem do linkera, więc kompilator i linker nie "zobaczą" wszystkich plików .cpp w tym samym czasie)?

PS: Mój kompilator: msvcpp 9 (ale sprawdzane na MinGW zbyt)

+0

Byłoby bardziej przydatne, gdybyś pokazał nam kod, który ** nie działa **. – JesperE

+0

Przypuszczam, że kod, który nie działa, to ten, w którym definiujesz zmienną w nagłówku, która jest zawarta w więcej niż jednym pliku (bez rozszerzenia), co powoduje kolizję nazw. – falstro

Odpowiedz

54

To dlatego, że definicja członka statycznego danych jest sam szablon. Zezwalanie na to jest konieczne z tego samego powodu, dla którego możesz mieć szablon funkcji, który nie jest wstawiany wiele razy w programie. Potrzebujesz szablonu do wygenerowania wynikowej jednostki (powiedzmy, funkcji lub statycznego elementu danych). Jeśli nie będą mogły umieścić definicję statycznego członka danych, jak byś wystąpienia następujących

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

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

Nie wiadomo, co T jest - Standardowy mówi definicja poza szablonem klasy jest definicja szablonu , w którym parametry są dziedziczone od właściciela szablonu klasy.


Przeprowadziłem eksperyment z GCC. Poniżej znajduje się jedno niejawne tworzenie instancji F<float>::value i jedna jawna specjalizacja F<char>::value, która musi być zdefiniowana w pliku .cpp, aby nie powodować powielonych błędów symboli, gdy są one dołączane wiele razy.

// 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() { } 

Druga jednostka tłumaczenie zawiera tylko kolejną ukrytą konkretyzacji tego samego statycznego członka danych

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

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

int test1 = F<float>::value; 

Oto co otrzymujemy z GCC - to sprawia, że ​​każdy niejawny konkretyzacji w słabych symboli i przykleja go do tu swoją własną sekcję. Słabe symbole nie powodują błędów, gdy istnieje wiele z nich w czasie połączenia. Zamiast tego, łącznik wybierze jedną instancję i odrzuca inne te zakładając wszystkie z nich są takie same

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 

Więc jak widać F<float>::value jest słaby symbol, który oznacza łącznik widać wielokrotność tych w czasie łącza .test, main i F<char>::value są symbolami globalnymi (nie słabymi). Łączenie main1.o i main2.o razem widzimy na wyjściu mapy (-Wl,-M) dodaje

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

To wskazuje, że faktycznie spada to wszystko z wyjątkiem jednej instancji.

+0

Ok. Ale jak linker, który widzi dwa "szablon Templ :: templStatic = 0;" definicje (w jednostkach1.cpp i unit2.cpp) radzą sobie z tą sytuacją? Czy w plikach obiektowych znajduje się specyficzna dla C++ meta informacja, która może powiedzieć linkerowi, że jedną definicję można zignorować (a w rezultacie nie mamy "linkera wielokrotnych definicji")? – cybevnm

+0

dodał kilka rzeczy GCC –

1

Istnieje rozwiązanie, można utworzyć klasę nadrzędną i umieścić zmienną statyczną w nim, a następnie dokonać klasa szablonu dziedziczą go prywatnie, oto przykład:

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; 
} 

wyjściowe będą:

Object 1 key is: 0 
Object 2 key is: 1 
Powiązane problemy