1. 程式人生 > >C++模板類(函式)編譯 多檔案編譯

C++模板類(函式)編譯 多檔案編譯

在C++中,模版和普通的函式或類有很多不一樣的性質。前兩天寫了一個模版類,標頭檔案和實現檔案分開存放的。這就出問題了,老是說找不到實現。查了一些資料才知道,原因是這樣的,編譯器在例項化一個類時,需要知道該類的所有確定的資訊,如果是普通的類這是完全由標頭檔案(.h)中類的宣告決定的。但是對於模版類,此資訊不確定,於是編譯器只是存放一個符號,而把這一個步驟放到最後連結時來完成。而編譯器在編譯模版類的實現檔案(.cpp)時沒有發現其他地方有這個類的例項化。最終,到連結階段找不到類模版的例項,出錯。

解決的辦法有兩個,把類的實現檔案放到宣告檔案中(或者在宣告檔案的最後include一下),另一個方法就是在實現時在函式頭部加上

export(不過此方法不是所有的編譯器都支援,在GCC編譯器下就出現了--警告: 關鍵字‘export’未實現,將被忽略 [預設啟用]--)。

=================================================================================

為了訪問其他編譯單元(如另一程式碼檔案)中的變數或物件,對普通型別(包括基本資料類、結構和類),可以利用關鍵字extern,來使用這些變數或物件時;但是對模板型別,則必須在定義這些模板類物件和模板函式時,使用標準C++新增加的關鍵字export(匯出/出口/輸出)。例如:

extern int n;

extern struct Point p;

extern class A a;

export template<class T> class Stack<int> s;

export template<class T> void f (T& t) {……}

一般是在標頭檔案中給出類的定義或全域性函式的宣告資訊,而在程式碼檔案中給出具體的(類成員函式或全域性函式的)函式定義。然後在多個使用者程式碼檔案中包含該標頭檔案後,就可以使用其中定義或宣告的類和函式。標頭檔案中一般不包含變數、結構和類物件的定義,因為這樣可能會導致重複定義的編譯錯誤。解決辦法是,在某個程式碼檔案中進行定義,在其他使用者程式碼檔案中用extern來引用它們。

但是對模板型別,則可以在標頭檔案中,宣告模板類和模板函式;在程式碼檔案中,使用關鍵字export來定義具體的模板類物件和模板函式;然後在其他使用者程式碼檔案中,包含宣告標頭檔案後,就可以使用該這些物件和函數了。例如:

// out.h:(宣告標頭檔案——只包含out函式的宣告資訊)

template<class T> void out (const T& t);

// out.cpp:(定義程式碼檔案——包含out函式的宣告[通過include]和定義等全部資訊)

#include <iostream>

#include “out.h”

export template<class T> void out (const T& t) {std::cerr << t;}

//user.cpp:(使用者程式碼檔案——包含函式的宣告標頭檔案後就可以使用該函式)

#include “out.h”

// 使用out()

說明:VC05目前還不支援export關鍵字(的編譯)。

===================================================================================================================================

這應該是令許多常用 VC6 的C++初學者頭疼的問題,我自然不例外了,特此蒐羅了相關問題分析。

********************************************************************************************************

大部分編譯器在編譯模板時都使用包含模式  
也就是一般使用的把模板放到標頭檔案中在包含

當你不使用這個模版函式或模版類
編譯器並不例項化它
當你使用時,編譯器需要例項化它,
因為編譯器是一次只能處理一個編譯單元
也就是一次處理一個cpp檔案
所以例項化時需要看到該模板的完整定義
所以都放在標頭檔案中


這不同於普通的函式,
在使用普通的函式時,編譯時只需看到該函式的宣告即可編譯
而在連結時由連結器來確定該函式的實體  

********************************************************************************************************

temp.h:
void fun(T);

temp.cpp:
#include "temp.h"
void fun(T){}

main.cpp:
#include "temp.h"
void main()
{
int a;
fun(a);
}

由於main.cpp用到了fun(a),所以在編譯main.cpp的時候,
編譯器知道它要用int來例項化fun(T)中的T,也就是要例項化fun(int),而要例項化一個函式模板就必須要知道這個函式模板的定義,但是由於main.cpp和包含函式模板定義的temp.cpp是分開編譯的,所以編譯器在編譯main.cpp的時候就不能夠用int來例項化fun(T),這樣編譯器就只能夠把
fun<int>(a)當成一個外部符號,交由linker來resolve。
另外編譯器在編譯temp.cpp的時候,同樣因為temp.cpp和main.cpp是分開編譯的,所以此時編譯器並不知道main.cpp中用到了fun<int>(a),所以此時編譯器雖然知道函式模板的定義,但也不會去例項化任何fun,當然就不會用int來例項化fun<T>了。
結果就是,你在main.obj引用了外部符號fun<int>,但是在temp.obj中不存在fun<int>,
所以linker就會報告無法解析的外部符號了

********************************************************************************************************

如果要把.h檔案和.cpp檔案分開,需要使用export關鍵字

要例項化一個函式模板就必須要知道這個函式模板的定義

********************************************************************************************************

C++ Primer 第三版 中文版
10.5 模板編譯模式:
"C++支援兩種模板編譯模式包含模式Inclusion Model 和分離模式Separation Model"
10.5.1 包含編譯模式
"在包含編譯模式下我們在每個模板被例項化的檔案中包含函式模板的定義並且往往把定義放在標頭檔案中像對行內函數所做的那樣"
10.5.2 分離編譯模式
"在分離編譯模式下函式模板的宣告被放在標頭檔案中"
"在模板定義中有一個關鍵字export"
"關鍵字export 告訴編譯器在生成被其他檔案使用的函式模板例項時可能需要這個模板定義編譯器必須保證在生成這些例項時該模板定義是可見的"
"關鍵字export 不需要出現在標頭檔案的模板宣告中"
"分離模式使我們能夠很好地將函式模板的介面同其實現分開進而組織好
程式以便把函式模板的介面放到標頭檔案中而把實現放在文字檔案中但是並不是所有的編譯器都支援分離模式即使支援也未必總能支援得很好支援分離模式需要更復雜的程式設計環境所以它們不能在所有C++編譯器實現中提供"
"Inside the C++ Object Model 描述了一個C++編譯器the Edison Design Group compiler支援的模板例項化機制"

很遺憾,
目前VC的任何版本(visual studio 2005未知)皆不支援分離模式!