標籤:isp amp post 為我 reference clu exit 其他 一個
一:傳統的編譯模型
使用C/C++進行編程時,一般會使用標頭檔以使定義和聲明分離,並使得程式以模組方式組織。將函式宣告、類的定義放在標頭檔中,而將函數實現以及類成員函數的定義放在獨立的檔案中。
但是對於模板來說,這種方式是行不通的,具體的例子如下:
首先是包含模板聲明的標頭檔temp.h:
//temp.h#ifndef TEMP_H#define TEMP_Htemplate<typename T>int compare(const T &a, const T &b);template<typename T>class testtemp{public: testtemp(const T &a):m_value(a){} void display();private: T m_value;};#endif
該標頭檔中包含了一個函數模板的聲明,以及一個類模板的定義。
下面是包含模板定義的源碼檔案temp.cpp:
//temp.cpp#include <iostream>#include "temp.h"template<typename T>int compare(const T &a, const T &b){ if (a < b) return -1; if (b < a) return 1; return 0;}template <typename T> void testtemp<T>::display(){ std::cout << m_value << std::endl;}
下面是主函數檔案main.cpp:
//main.cpp#include <iostream>#include "temp.h"int main(){ int a = 1, b = 3; int res; testtemp<int> tt(4); tt.display(); res = compare(a, b); std::cout << "res is " << res << std::endl;}
對上面的檔案編譯產生可執行檔時,會報錯:
# g++ -o main main.cpp temp.cpp/tmp/ccNwfO8x.o: In function `main‘:main.cpp:(.text+0x47): undefined reference to `testtemp<int>::display()‘main.cpp:(.text+0x5a): undefined reference to `int compare<int>(int const&, int const&)‘collect2: error: ld returned 1 exit status
報錯的原因如下:
C++中每一個對象所佔用的空間大小,都是在編譯的時候就確定的。在編譯階段,源碼檔案main.cpp將包含模板聲明的標頭檔temp.h包含進來之後,編譯器就需要為main.cpp中涉及到的每個對象產生合適的記憶體布局,為每個函數產生相應的指令。
當源碼檔案main.cpp中涉及到模板類成員函數或者模板函數的調用時,因為模板函數的定義在另一個源碼檔案temp.cpp中,編譯器目前僅僅知道它們的聲明。所以,在main.cpp中調用到的的testtemp<int>::display函數,以及int compare<int>(int const&, int const&)函數,編譯器認為這些函數的實現是在其他源碼檔案中的,編譯器不會報錯,因為連接器會最終將所有的二進位檔案進行串連,從而完成符號尋找,形成一個可執行檔。
儘管編譯器也編譯了包含模板定義的源碼檔案temp.cpp,但是該檔案僅僅是模板的定義,而並沒有真正的執行個體化出具體的函數來。因此在連結階段,編譯器進行符號尋找時,發現源碼檔案中的符號,在所有二進位檔案中都找不到相關的定義,因此就報錯了。
二:模板的編譯模型
當編譯器看到模板定義的時候,它不立即產生代碼。只有在看到用到模板時,如調用了函數模板或調用了類模板的對象的時候,編譯器才產生特定類型的模板執行個體。
一般而言,當調用函數的時候,編譯器只需要看到函數的聲明。類似地,定義類類型的對象時,類定義必須可用,但成員函數的定義不是必須存在的。因此,應該將類定義和函式宣告放在標頭檔中,而普通函數和類成員函數的定義放在源檔案中。
模板則不同:要進行執行個體化,編譯器必須能夠訪問定義模板的原始碼。當調用函數模板或類模板的成員函數的時候,編譯器需要函數定義,需要那些通常放在源檔案中的代碼。
標準 C++ 為編譯模板代碼定義了兩種模型。分別是包含編譯模型和分別編譯模型。
所謂包含編譯模型,說白了,就是將函數模板的定義放在標頭檔中。因此,對於上面的例子,就是將temp.cpp的內容都放到temp.h中。
包含編譯模型有個問題,如果兩個或多個單獨編譯的源檔案使用同一模板,這些編譯器將為每個檔案中的模板產生一個執行個體。因此給定模板會產生多個相同的執行個體,在連結的時候,編譯器會選擇一個執行個體化而丟棄其他的。
在分別編譯模型中,編譯器會為我們跟蹤相關的模板定義。但是,我們必須讓編譯器知道要記住給定的模板定義,因此需要使用 export 關鍵字。但是,實際上很多編譯器都不支援這個關鍵字,而且C++11 將這個關鍵字設定為 unsued 和 reserved 了。
所以,結論就是,把模板的定義和實現都放到標頭檔中。
參考:
http://gaunthan.leanote.com/post/C-%E6%A8%A1%E6%9D%BF%E7%9A%84%E7%BC%96%E8%AF%91%E6%A8%A1%E5%9E%8B
https://www.zhihu.com/question/20630104
C++模板編譯模型