一般情況下,編寫一個類一般將頭文件(.h文件)和源文件(.cpp文件)進行分離。.h文件定義類和函數,.cpp文件中進行函數的實現, 然后提供給其他.cpp文件調用。但是在使用模板時,這種習慣性做法將變得不再有用,因為當實例化一個模板時,編譯器必須看到模板確切的定義,而不僅僅是它的聲明。
模板定義很特殊,由template<…>處理的任何東西都意味着編譯器在當時不為它分配存儲空間(沒有具體的函數時不會對模板實例化),它一直處於等待狀態直到被一個模板實例告知。在編譯器和連接器的某一處,有一機制能去掉指定模板的多重定義。所以為了容易使用,幾乎總是在頭文件中放置全部的模板聲明和定義。
由於C++是獨立編譯,例如a.cpp、b.cpp、c.cpp三個文件,先獨立編譯成三個獨立的目標文件,即a.o、b.o、c.o,然后再通過鏈接器鏈接起來,生成可執行文件。
在編譯時,a.cpp發現一個函數調用,在當前文件找不到函數定義,則在函數位置生成符號,在鏈接時,再尋找這個函數。
模板類是兩次編譯。第一次編譯時只對模板進行編譯,不生成具體函數,在調用時才生成具體函數。為什么不能在第一次編譯就生成具體函數呢?請看下圖(取自vector模板源碼迭代器“+”的定義和聲明):
標准要求編譯器(VS、GCC)在實例化模板時必須在上下文中可以查看到其定義實體;而反過來,在看到實例化模板之前,編譯器對模板的定義體是不處理的——原因很簡單,編譯器怎么會預先知道“typename” 實參是什么類型呢?因此模板的實例化與定義體必須放到同一翻譯單元中。
所以,最好的辦法就是將模板的聲明和定義都放置在同一個.h文件中。這同時也是為什么所有的STL頭文件都包含模板定義的原因。