在 C++ 中為了操作簡潔引入了函式模板。所謂的函式模板實際上是建立一個通用函式,其函式型別或形參型別不具體指定,用一個虛擬的型別來表達,這個通用函式就稱為函式模板。
1、通用的寫法
函式模板不是一個具體的函式,編譯器不能為其生成可執行程式碼。定義函式模板後只是一個對函式功能框架的描述,當它具體執行時,將根據傳遞的實際引數決定其功能。為了容易使用,一般通用的寫法都是在標頭檔案中直接定義函式模板,定義的同時也是宣告該函式,供給其它檔案包含呼叫。
1 //------fun.h或fun.hpp------// 2 #ifndef _FUN_H_ 3 #define _FUN_H_ 4 5 using namespace std; 6 7 template<typename T> 8 void fun(int b, T c, T d) //定義函式模板 9 { 10 ...... 11 } 12 13 #endif
對編譯器而言,定義函式模板的時候,編譯器並不會對它進行編譯,因為它沒有一個實體可用,編譯器只看到了宣告,只有模板被例項化後(用在特定的型別上),編譯器才會根據具體的型別對模板進行編譯。因此當在別的檔案中呼叫該函式模板時,根據傳遞的實際引數決定其功能,這樣編譯器就可以在編譯期間看到模板函式的定義並完成模板的例項化,如果在編譯的時候,找不到模板函式的定義,就先不在這一次編譯中例項化該模板函式。
2、問題的引出
但是標頭檔案中定義和使用函式模板時,碰到了一個這樣的場景,即在函式模板中使用到了全域性變數:
1 //------fun.h或fun.hpp------// 2 #ifndef _FUN_H_ 3 #define _FUN_H_ 4 5 using namespace std; 6 int a; //定義全域性變數 7 template<typename T> 8 void fun(int b, T c, T d) //定義函式模板 9 { 10 ...... 11 a = b; 12 } 13 14 #endif
因此碰到其它多個檔案需要使用該函式模板時,都需要各自包含該函式模板的標頭檔案,編譯時就會出現“全域性變數重複定義”的錯誤。
嘗試按照普通函式定義和宣告分開的思路將函式模板的定義和宣告分開:
原始檔:
1 //------fun.cpp------// //錯誤做法 2 using namespace std; 3 int a; //定義全域性變數 4 template<typename T> 5 void fun(int b, T c, T d) //定義函式模板 6 { 7 ...... 8 a = b; 9 }
標頭檔案:
1 //------fun.h或fun.hpp------// //錯誤做法 2 #ifndef _FUN_H_ 3 #define _FUN_H_ 4 5 extern a; 6 template<typename T> void fun(int b, T c, T d); 7 8 #endif
經過嘗試,按照普通函式的方式將函式模板的定義和宣告分開,在其它檔案中呼叫函式模板,編譯時就會出現“找不到該函式定義”的錯誤。
那麼有沒有辦法將函式模板的定義和宣告正確分開,提供給其它檔案包含呼叫呢,答案肯定是有的。
3、問題的解決
針對上述第2點所闡述的函式模板使用的這一場景,需要將函式模板的定義和宣告分離開來,根據實際的應用,使用以下的做法可以很好的解決這一問題,編譯和呼叫都沒有問題。
首先是原始檔*.cpp的實現:
1 //------fun.cpp------// 2 using namespace std; 3 int a; //定義全域性變數 4 template<typename T> 5 void fun(int b, T c, T d) //定義函式模板 6 { 7 ...... 8 a = b; 9 } 10 11 template void fun(int b, int c, int d); //函式模板例項化,此時T被int替代 12 template void fun(int b, char c, char d); //函式模板例項化,此時T被char替代
因此在原始檔中操作有:
(1)、定義需要使用的函式模板;
(2)、在定義的函式模板後進行函式例項化操作,透過這樣的方法實現具體的模板函式。
接著是標頭檔案*.h或者*.hpp的實現:
1 //------fun.h或fun.hpp------// 2 #ifndef _FUN_H_ 3 #define _FUN_H_ 4 5 extern a; 6 template<typename T> void fun(int b, T c, T d); 7 extern template void fun(int b, int c, int d); 8 extern template void fun(int b, char c, char d); 9 10 #endif
因此在標頭檔案中需要的操作有:
(1)、宣告定義的函式模板;
(2)、使用extern的方式宣告例項化後的模板函式。
總結
可見,將函式模板的定義和宣告分開,需要額外在原始檔中進行函式模板的例項化再在標頭檔案中進行宣告,多了一些步驟。在無特定的使用的場景中,還是建議將函式模板放在標頭檔案中直接定義並呼叫;當然,如果碰到一些跨檔案呼叫的特定場景,那麼採用這種將函式模板的定義和宣告分開的方法也是OK的。
更多技術內容和書籍資料獲取敬請關注微信公眾號“明解嵌入式”