c++函數模板聲明與定義相分離


  最近在仿寫stl,發現stl源碼中將模板的聲明與定義寫在一起實在很不優雅。自己嘗試用“傳統”方法,及在.h文件里聲明,在.cpp文件里定義,然后在main函數里包含.h頭文件,這樣會報鏈接錯誤。這是因為函數模板要被實例化后才能成為真正的函數,在使用函數模板的源文件中包含函數模板的頭文件,如果該頭文件中只有聲明,沒有定義,那編譯器無法實例化該模板,最終導致鏈接錯誤。

  上面這句話有點抽象。要理解為什么會出錯,首先要理解用傳統方法寫非模板函數時,編譯器是怎么運作的。舉個例子

//---------------test.h-------------------// 
 void f();//這里聲明一個函數f 
//---------------test.cpp--------------// 
 #include”test.h” 
 void f() 
 { 
 …//do something 
 } //這里實現出test.h中聲明的f函數 
//---------------main.cpp--------------// 
 #include”test.h” 
 int main() 
 { 
     f(); //調用f
 } 

編譯時會生成兩個obj文件,main.obj和test.obj,而在main.obj里並沒有f函數的二進制代碼,這些代碼實際存在於test.obj中。在main.obj中對f的調用只會生成一行call指令,call指令的地址由鏈接器生成。

再看一個函數模板的例子

//-------------test.h----------------// 
 template<class T> 
 class A 
 { 
    public: 
     void f(); //這里只是個聲明 
 }; 
//---------------test.cpp-------------// 
 #include”test.h” 
 template<class T> 
 void A<T>::f() 
 { 
 …//do something 
 } 
//---------------main.cpp---------------// 
 #include”test.h” 
 int main() 
 { 
     A<int> a; 
    a. f(); 
 } 

 我們知道模板有個具現化的過程,在未被使用的時候是不會生成二進制文件的。所以當鏈接器去找f函數的地址時,因為在這之前沒有調用過f(),test.obj里自然就沒有f函數的二進制代碼,於是就會報錯。

要使模板聲明與定義分開也不是沒有辦法。

第一種辦法是在main函數里包含cpp文件

//-------------test.h----------------// 
 template<class T> 
 class A 
 { 
    public: 
     void f(); //這里只是個聲明 
 }; 
//---------------test.cpp-------------// 
 #include”test.h” 
 template<class T> 
 void A<T>::f() 
 { 
 …//do something 
 } 
//---------------main.cpp---------------// 
 #include”test.cpp” 
 int main() 
 { 
     A<int> a; 
    a. f(); 
 } 

 這樣三個文件的內容通過include實際上包含在同一個文件里,自然就不會出錯了。同理,還可以這樣

//-------------test.h----------------// 
 template<class T> 
 class A 
 { 
    public: 
     void f(); //這里只是個聲明 
 }; 
#include<test_impl.h>
//---------------test_impl.h-------------// 
 template<class T> 
 void A<T>::f() 
 { 
 …//do something 
 } 
//---------------main.cpp---------------// 
 #include”test.h” 
 int main() 
 { 
     A<int> a; 
    a. f(); 
 } 

 這兩種方法實際上都是包含編譯,沒有本質的區別,不過感覺第二種方法看起來比較舒服。還有一種比較hack的方法

 

//-------------test.h----------------// 
 template<class T> 
 class A 
 { 
    public: 
     void f(); //這里只是個聲明 
 }; 
//---------------test.cpp-------------// 
 #include”test.h” 
 template<class T> 
 void A<T>::f() 
 { 
 …//do something 
 } 
template class A<int>;
//---------------main.cpp---------------// 
 #include”test.h” 
 int main() 
 { 
     A<int> a; 
    a. f(); 
 } 

這樣由於在test.cpp里實例化了A<int>,所以鏈接器能夠找到相關代碼,就不會報錯。但是這樣main函數要用哪種類型的模板都得在test.cpp先實例化,很不方便。

以上都是包含編譯的方法,其實C++0x有個export關鍵字,使模板能分離編譯,但是基本沒有編譯器支持,在c++11中就廢除掉了,所以這里就不提了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM