2009-10-12 98 views
48

我不明白,爲什麼如果我們在頭中定義通常(非模板)類的靜態變量,我們有鏈接器錯誤,但在模板的情況下一切正常,而且我們將有單個實例所有的翻譯單位之間的靜態變量:模板靜態變量

它的模板頭(template.h):

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

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

這是第一部使用模板(unit1.cpp)

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

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

第二單元他重(unit2.cpp):

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

最後,main.cpp中:

0 
1 

// main.cpp 
#include <iostream> 
int method1(); 
int method2(); 

int main(int argc, char** argv) { 
    std::cout << method1() << std::endl; 
    std::cout << method2() << std::endl; 
} 

compilling,鏈接和執行該代碼後,我們將輸出以下

那麼,爲什麼在模板的情況下工作正常(和預期的一樣)?編譯器或鏈接器如何處理這個問題(我們可以在分別調用編譯器的情況下編譯每個.cpp文件,然後將它們鏈接到鏈接器,因此編譯器和鏈接器不會同時「看到」所有.cpp文件)?

PS:我的編譯器:msvcpp 9(但查了MinGW的太)

+0

如果你向我們展示了**沒有工作的代碼,它會更有用。 – JesperE 2009-10-12 10:43:01

+0

我想代碼不起作用的是你在頭文件中定義了一個變量,該頭文件被包含在多個文件中(沒有出現),這會導致命名衝突。 – falstro 2009-10-12 10:46:15

回答

54

這是因爲靜態數據成員的定義本身就是一個模板。出於同樣的原因,允許這樣做是必要的,因爲允許您在程序中使用多次內聯的函數模板。您需要該模板來生成結果實體(例如,函數或靜態數據成員)。如果你將不會被允許把靜態數據成員的定義,你會如何實例以下

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

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

它不知道什麼T是 - 標準說類模板外的定義是一個模板定義,其中參數是從它的類模板所有者繼承的。


我對GCC做了一些實驗。在下文中,我們有一個隱含的實例化F<float>::value,以及一個明確的專用化F<char>::value,它必須在.cpp文件中定義,以便在多次包含時不會導致重複的符號錯誤。

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

第二個翻譯單元包含相同的靜態數據成員

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

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

int test1 = F<float>::value; 

這裏只是一個隱含的實例化,我們得到與海灣合作委員會的東西 - 它使每一個隱式實例爲弱符號,它堅持到它自己的部分在這裏。當鏈接時存在多個符號時,弱符號不會導致錯誤。相反,鏈接器會選擇一個實例,並丟棄其他的假設所有的人都是一樣的

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 

所以我們可以看到F<float>::value是弱符號,這意味着連接可以在鏈接時看到的這些多。testmainF<char>::value是全局(非弱)符號。鏈接main1.omain2.o在一起,我們可以看到在地圖輸出(-Wl,-M)以下

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

這表明,實際上它丟棄所有除一個實例。

+0

好的。但如何鏈接器,其中看到兩個「模板 Templ :: templStatic = 0;」定義(在unit1.cpp和unit2.cpp中)處理這種情況?目標文件是否有一些特定於C++的元信息,可以告訴鏈接器,一個定義可以被忽略(並且,因爲我們沒有「多個定義」鏈接器錯誤)? – cybevnm 2009-10-12 11:34:21

+0

增加了一些GCC的東西 – 2009-10-12 11:37:31

1

有解決方案,你可以創建一個父類,把靜態變量中,然後讓你的模板類繼承私下,這裏有一個例子:

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

輸出將是:

Object 1 key is: 0 
Object 2 key is: 1