1. 程式人生 > >透徹理解c++模板包含模型(轉)

透徹理解c++模板包含模型(轉)

原文的地址是:http://blog.csdn.net/ixsea/article/details/6695496
觀點
包含模型是C++模板原始碼的一種組織方式,它鼓勵將模板程式碼全部放在一個.h標頭檔案中,這樣可以避免莫名其妙的連結錯誤。
莫名其妙的連結錯誤
一般而言,程式設計師習慣將函式和類的宣告放在.h檔案、把它們的實現放在.cpp檔案,這種多檔案組織方式一直被倡導。一方面,這種分離使得程式碼邏輯清晰,想要了解程式用到哪些全域性函式和類,只要檢視.h檔案就可以。如果把宣告和實現都揉在一起,帶來的麻煩可想而知,要在一堆亂糟糟的程式碼中尋找函式名、類名、成員名是一種折磨。另一方面,在構建動態連結庫時,這種組織方式是必需的。因為動態連結庫是二進位制級別上的程式碼複用,它的一大優點就是具體的實現過程被隱藏起來,全部揉在一個.h檔案中顯然不符合要求。
然而不幸的是,當程式設計師仍然按照這種好的習慣編寫模板程式碼時,卻出現了問題。比如下面這個簡單的例子:

// Bigger.h  
template<typename T>  
T Bigger(T,T);  

//Bigger.cpp  
#include"Bigger.h"  
template<typename T>  
T Bigger(T a,T b)  
{  
    return a>b?a:b;  
}  

//main.cpp  
#include"Bigger.h"  
#include<iostream>  
using namespace std;  
int main()  
{  
    cout<<Bigger(10
,20)<<endl; system("pause"); return 0; }

這幾行程式碼很簡單,分成了三個檔案Bigger.h、Bigger.cpp以及main.cpp,分別對應模板函式Bigger的宣告、定義和使用。看起來結構清晰,符合好的編碼習慣,編譯連結卻得到這樣的錯誤提示:

Error 1 error LNK2019: unresolved external symbol "int __cdecl Bigger<int>(int,int)" (??$Bigger@H@@YAHHH@Z) referenced in function _main E:\Codes\Chapter3Lab\includeModel\main.obj

意思是連結器找不到main.obj裡Bigge函式的實現。這種看起來毫無道理的連結錯誤,也很好的體現了模板的例項化規則。
模板的例項化規則
對於模板函式來說,只有被呼叫的模板函式才被例項化,這裡的被呼叫並不要求它必須被main函式呼叫。某個普通函式呼叫了模板函式,該模板函式就將對應產生一個例項,而呼叫它的普通函式可能並不被main呼叫,也即有可能並不被執行。
模板類也有型別的例項化規則,特別的是即使顯式例項化了類模板,類模板的成員函式也未必被例項化,這是模板類的“不完全”例項化規則.
連結錯誤的解釋
瞭解了模板的例項化規則,就可以對上面的連結錯誤做出解釋了。main.cpp中呼叫了Bigger(10,20),按理說這將引起模板函式Bigger(T,T)被例項化為普通函式,然而在main.cpp所屬的翻譯單元裡並沒有Bigger(T,T)的實現,對main.cpp所屬的翻譯單元來說,Bigger(T,T)的實現是不可見的。因此,由main.cpp所屬翻譯單元編譯得到main.obj時,編譯器假設Bigger(int,int)在其它翻譯單元中。
Bigger.cpp雖然有Bigger(T,T)的實現,但是由於在Bigger.cpp所屬翻譯單元中Bigger並沒有被呼叫,因此Bigger.cpp就沒有義務對模板函式Bigger(T,T)進行例項化,於是由它產生的Bigger.obj中也找不到的Bigger(int,int)。
本文前述例子中的連結錯誤資訊正是表達的這個意思。
連結錯誤的進一步探討
既然是因為Bigger.cpp沒有義務對Bigger(T,T)進行例項化,那麼在Bigger.cpp中增加一個呼叫Bigger(int,int)函式的普通函式是否就可以了呢?在Bigger.cpp檔案中添幾行程式碼,如下所示:

//Bigger.cpp  
#include"Bigger.h"  
template<typename T>  
T Bigger(T a,T b)  
{  
    return a>b?a:b;  
}  
void g()  //增加一個呼叫Bigger<int>(int,int)的普通函式g()  
{  
    Bigger(1,2);  
}  

編譯、連結成功,允許結果正確,進一步驗證了上述觀點。
解決方法 - 包含模型
本文列出的例子很簡單,規模小,所以按照模板的例項化規則,“人為”地介入到模板函式的例項化過程中並讓程式成功執行。但是,在規模較大的程式裡,想要人為介入加以控制幾乎是不可能的,應該使用C++推薦的包含模型。
具體做法並不複雜:把模板的宣告和定義放在一個.h檔案中,凡是用到該模板的.cpp檔案包含它所在的.h檔案就可以了。上面的例子使用包含模型改寫,最終是程式碼是這樣的:

// Bigger.h  
template<typename T>  
T Bigger(T a,T b)  
{  
    return a>b?a:b;  
}  

//main.cpp  
#include"Bigger.h"  
#include<iostream>  
using namespace std;  
int main()  
{  
    cout<<Bigger(10,20)<<endl;  
    system("pause");  
    return 0;  
}  

不過仍然有一個問題值得思考:當多個.cpp檔案同時包含Bigger.h時,就有可能產生多份相同型別的例項化,這樣是否會造成最終生成的.exe檔案變得龐大?這個問題理論上是存在的,不過現在大多數編譯器都對此作了一定的優化,一個模板的相同型別有多份例項化體時,編譯器最終只保留一個,這樣就避免了“程式碼膨脹”的問題。

相關推薦

透徹理解c++模板包含模型()

原文的地址是:http://blog.csdn.net/ixsea/article/details/6695496 觀點 包含模型是C++模板原始碼的一種組織方式,它鼓勵將模板程式碼全部放在一個.h標頭檔案中,這樣可以避免莫名其妙的連結錯誤。 莫名其妙的連

C++模板編譯模型包含編譯模型

    在包含編譯模型中,編譯器必須看到用到的所有模板的定義。一般而言,可以通過在宣告模板函式或類模板的標頭檔案中新增一條#include指示使定義可用,該#include引入了包含相關定義的原始檔。 1.header file  tt.h #ifndef TT_H #def

C++模板編譯模型

pre str 查找 enc 生成可執行文件 turn 對象 endif 使用 一:傳統的編譯模型 使用C/C++進行編程時,一般會使用頭文件以使定義和聲明分離,並使得程序以模塊方式組織。將函數聲明、類的定義放在頭文件中,而將函數實現以及類成員函數的定義放

跟我一起透徹理解template模板模式

itl names turn log select -s style for ont #include <iostream> using namespac

理解C++變數儲存模型

在理解程式記憶體一文中我們介紹了普通程式執行時在記憶體中的佈局,下面我們專門針對C++原始碼以WinDbg為工具分析下C++程式的變數儲存模型, 要理解下面的知識,請先看懂理解程式記憶體一文。下面我們嘗試分析C++變數的儲存模型, 我們的測試程式非常簡單:#include <iostream

深入探討vc下C++模板編譯模型

寫過模板的朋友也許知道,一個模板程式,當編譯器看到模板定義時並不立即產生程式碼,只有在我們用到模板,並對其例項化的時候,才會產生特定的例項。此時,編譯器就要訪問定義模板的原始碼了。如果原始碼不可訪問,當然,編譯器會報錯的。記得我初學的時候,採用的是直接將宣告和實現全部放在一

透徹理解C++中const的含義

關於const修飾變數、引數、返回值、成員函式不同的意義,網上有很多講解的,這裡不再贅述。在Dan Saks的一篇講解const的文章中非常清楚的從編譯器角度講了const的含義,本文只做一定的總結,原文參考http://blog.csdn.net/bianbi

C++ 模板詳解(二)(

創建 規則 error ++ 例如 public err iostream () 四、類模板的默認模板類型形參   1、可以為類模板的類型形參提供默認值,但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認值。   2、類模板的類型形

C++跟我一起透徹理解虛函數表

技術 覆蓋 text 編譯 pretty ring 對象 virt roc //首先讓我們來了解類對象的構造順序。 #include <iostream> using namespace std; class A { public:

《深入理解計算機系統》關於csapp.h和csapp.c的編譯問題()

系統 文件中 class net 工作 inux 而且 pan div 編譯步驟如下: 1.我的當前工作目錄為/home/sxh2/clinux,目錄下有3個文件inet_aton.c csapp.h csapp.c。 2.編譯csapp.c文件,命令為gcc -c csa

全面理解Java內存模型(JMM)及volatile關鍵字(

java 關鍵字 最新版本 zed 相互 虛擬機 集成 反射機制 寄存器 原文地址: 全面理解Java內存模型(JMM)及volatile關鍵字 關聯文章: 深入理解Java類型信息(Class對象)與反射機制 深入理解Java枚舉類型(enum) 深入理解Java註

通俗理解N-gram語言模型。(

資料 簡化 事情 自然 自然語言 規模 什麽 發音 給定 N-gram語言模型 考慮一個語音識別系統,假設用戶說了這麽一句話:“I have a gun”,因為發音的相似,該語音識別系統發現如下幾句話都是可能的候選:1、I have a gun. 2、I have a gu

C++標準庫和標準模板庫【

(轉自:https://blog.csdn.net/rl529014/article/details/51154798) C++強大的功能來源於其豐富的類庫及庫函式資源。C++標準庫的內容總共在50個標準標頭檔案中定義。 在C++開發中,要儘可能地利用標準庫完成。這樣做的直接好處包括

C++筆記 第五十七課 深入理解函式模板---狄泰學院

如果在閱讀過程中發現有錯誤,望評論指正,希望大家一起學習,一起進步。 學習C++編譯環境:Linux 第五十七課 深入理解函式模板 1.函式模板 函式模板深入理解 編譯器從函式模板通過具體型別產生不同的函式 編譯器會對函式模板進行兩次編譯 對模板程式碼本身進行編譯 對引數替換

C++模板詳解

模板是C++支援引數化多型的工具,使用模板可以使使用者為類或者函式宣告一種一般模式,使得類中的某些資料成員或者成員函式的引數、返回值取得任意型別。   模板是一種對型別進行引數化的工具;   通常有兩種形式:函式模板和類模板;   函式模板針對僅引數型別不

深入理解C++物件模型之建構函式

一、前言     學習C++的同學一般都知道有建構函式這個東西,我相信很多同學的理解就是建構函式是用來初始化類成員的,是的,建構函式的本質確實是這樣的,但很多同學會有以下兩個誤解:         (1)任何class如果沒有定義任何建構函式,編譯器就會幫你自動生成一個;

C++模板類之理解編譯器的編譯模板過程

//-------------test.h----------------// template<class T> class A { public: void f(); //這裡只是個宣告 }; //---------------test.cpp-------------// #

C++學習筆記 模板 包含編譯模式 分別編譯模式

看起來沒有什麼問題吧,但是你編譯一下,依然報錯。不是麼? error LNK2005: "void __cdecl Work(int)"([email protected]@@[email protected]) already defined in func.obj 又是重定義!!我們

】深入理解C++的動態繫結和靜態繫結

為了支援c++的多型性,才用了動態繫結和靜態繫結。理解他們的區別有助於更好的理解多型性,以及在程式設計的過程中避免犯錯誤。 需要理解四個名詞: 1、物件的靜態型別:物件在宣告時採用的型別。是在編譯期確定的。 2、物件的動態型別:目前所指物件的型別。是在執行期決定的。物件的

理解C++虛擬函式絕好的文章

0.說明      一個多月前微軟實習生來了一次電面,第一個問題便問了虛擬函式和純虛擬函式的區別,由於學了太久了(大一學的,而且我的記性真的很差- -!)沒能回答上來,在此後的一個多小時的電面中,對方再未問更深的技術問題,只是問了做了哪些專案云云。最後面試結束,面試官讓我