版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
1、c 模板類 1 理解編譯器的編譯模板過程c+模板類(1)理解編譯器的編譯模板過程2010-04-13 09:40如何組織編寫模板程序前言常遇到詢問使用模板到底是否容易的問題,我的回答是:模板的使用是容易的,但組織編寫卻不容易??纯次覀儙缀趺刻於寄苡龅降哪0孱惏桑鏢TL,ATL,WTL,以及Boost的模板類,都能體會到這樣的滋味:接口簡單,操作復(fù)雜。我在5年前開始使用模板,那時我看到了MFC的容器類。直到去年我還沒有必要自己編寫模板類??墒窃谖倚枰约壕帉懩0孱悤r,我首先遇到的事實(shí)卻是傳統(tǒng)編程方法(在*.h文件聲明,在*.cpp文件中定義)不能用于模板。于是我花費(fèi)一些時間來了解問題所在及其解
2、決方法。本文對象是那些熟悉模板但還沒有很多編寫模板經(jīng)驗(yàn)的程序員。本文只涉及模板類,未涉及模板函數(shù)。但論述的原則對于二者是一樣的。問題的產(chǎn)生通過下例來說明問題。例如在array.h文件中有模板類array:/array.h template typename T,int SIZE class arrayT data_SIZE;array(const array&other);const array&operator=(const array&other);public:array();T&operator(int i)return data_i;const T&get_elem(int i)co
3、nstreturn data_i;void set_elem(int i,const T&value)data_i=value;operator T*()return data_;然后在main.cpp文件中的主函數(shù)中使用上述模板:/main.cpp#includearray.hint main(void)array int,50 intArray;intArray.set_elem(0,2);int firstElem=intArray.get_elem(0);int*begin=intArray;這時編譯和運(yùn)行都是正常的。程序先創(chuàng)建一個含有50個整數(shù)的數(shù)組,然后設(shè)置數(shù)組的第一個元素值為2,
4、再讀取第一個元素值,最后將指針指向數(shù)組起點(diǎn)。但如果用傳統(tǒng)編程方式來編寫會發(fā)生什么事呢?我們來看看:將array.h文件分裂成為array.h和array.cpp二個文件(main.cpp保持不變)/array.h template typename T,int SIZE class arrayT data_SIZE;array(const array&other);const array&operator=(const array&other);public:array();T&operator(int i);const T&get_elem(int i)const;void set_elem
5、(int i,const T&value);operator T*();/array.cpp#includearray.htemplate typename T,int SIZE T&array T,SIZE:operator(int i)return data_i;template typename T,int SIZE const T&array T,SIZE:get_elem(int i)constreturn data_i;template typename T,int SIZE void array T,SIZE:set_elem(int i,const T&value)data_i
6、=value;template typename T,int SIZE array T,SIZE:operator T*()return data_;編譯時會出現(xiàn)3個錯誤。問題出來了:為什么錯誤都出現(xiàn)在第一個地方?為什么只有3個鏈接出錯?array.cpp中有4個成員函數(shù)。要回答上面的問題,就要深入了解模板的實(shí)例化過程。模板實(shí)例化程序員在使用模板類時最常犯的錯誤是將模板類視為某種數(shù)據(jù)類型。所謂類型參量化(parameterized types)這樣的術(shù)語導(dǎo)致了這種誤解。模板當(dāng)然不是數(shù)據(jù)類型,模板就是模板,恰如其名:編譯器使用模板,通過更換模板參數(shù)來創(chuàng)建數(shù)據(jù)類型。這個過程就是模板實(shí)例化(Inst
7、antiation)。從模板類創(chuàng)建得到的類型稱之為特例(specialization)。模板實(shí)例化取決于編譯器能夠找到可用代碼來創(chuàng)建特例(稱之為實(shí)例化要素,point of instantiation)。要創(chuàng)建特例,編譯器不但要看到模板的聲明,還要看到模板的定義。模板實(shí)例化過程是遲鈍的,即只能用函數(shù)的定義來實(shí)現(xiàn)實(shí)例化。再回頭看上面的例子,可以知道array是一個模板,array int,50是一個模板實(shí)例-一個類型。從array創(chuàng)建array int,50的過程就是實(shí)例化過程。實(shí)例化要素體現(xiàn)在main.cpp文件中。如果按照傳統(tǒng)方式,編譯器在array.h文件中看到了模板的聲明,但沒有模板的定
8、義,這樣編譯器就不能創(chuàng)建類型array int,50。但這時并不出錯,因?yàn)榫幾g器認(rèn)為模板定義在其它文件中,就把問題留給鏈接程序處理?,F(xiàn)在,編譯array.cpp時會發(fā)生什么問題呢?編譯器可以解析模板定義并檢查語法,但不能生成成員函數(shù)的代碼。它無法生成代碼,因?yàn)橐纱a,需要知道模板參數(shù),即需要一個類型,而不是模板本身。這樣,鏈接程序在main.cpp或array.cpp中都找不到array int,50的定義,于是報出無定義成員的錯誤。至此,我們回答了第一個問題。但還有第二個問題,在array.cpp中有4個成員函數(shù),鏈接器為什么只報了3個錯誤?回答是:實(shí)例化的惰性導(dǎo)致這種現(xiàn)象。在main.
9、cpp中還沒有用上operator,編譯器還沒有實(shí)例化它的定義。解決方法認(rèn)識了問題,就能夠解決問題:在實(shí)例化要素中讓編譯器看到模板定義。用另外的文件來顯式地實(shí)例化類型,這樣鏈接器就能看到該類型。使用export關(guān)鍵字。前二種方法通常稱為包含模式,第三種方法則稱為分離模式。第一種方法意味著在使用模板的轉(zhuǎn)換文件中不但要包含模板聲明文件,還要包含模板定義文件。在上例中,就是第一個示例,在array.h中用行內(nèi)函數(shù)定義了所有的成員函數(shù)。或者在main.cpp文件中也包含進(jìn)array.cpp文件。這樣編譯器就能看到模板的聲明和定義,并由此生成array int,50實(shí)例。這樣做的缺點(diǎn)是編譯文件會變得很大
10、,顯然要降低編譯和鏈接速度。第二種方法,通過顯式的模板實(shí)例化得到類型。最好將所有的顯式實(shí)例化過程安放在另外的文件中。在本例中,可以創(chuàng)建一個新文件templateinstantiations.cpp:/templateinstantiations.cpp#includearray.cpptemplate class array int,50;/顯式實(shí)例化array int,50類型不是在main.cpp中產(chǎn)生,而是在templateinstantiations.cpp中產(chǎn)生。這樣鏈接器就能夠找到它的定義。用這種方法,不會產(chǎn)生巨大的頭文件,加快編譯速度。而且頭文件本身也顯得更加干凈和更具有可讀性。
11、但這個方法不能得到惰性實(shí)例化的好處,即它將顯式地生成所有的成員函數(shù)。另外還要維護(hù)templateinstantiations.cpp文件。第三種方法是在模板定義中使用export關(guān)鍵字,剩下的事就讓編譯器去自行處理了。當(dāng)我在Stroustrup的書中讀到export時,感到非常興奮。但很快就發(fā)現(xiàn)VC 6.0不支持它,后來又發(fā)現(xiàn)根本沒有編譯器能夠支持這個關(guān)鍵字(第一個支持它的編譯器要在2002年底才問世)。自那以后,我閱讀了不少關(guān)于export的文章,了解到它幾乎不能解決用包含模式能夠解決的問題。欲知更多的export關(guān)鍵字,建議讀讀Herb Sutter撰寫的文章。結(jié)論要開發(fā)模板庫,就要知道模
12、板類不是所謂的原始類型,要用其它的編程思路。本文目的不是要嚇唬那些想進(jìn)行模板編程的程序員。恰恰相反,是要提醒他們避免犯下開始模板編程時都會出現(xiàn)的錯誤。/甚至是在定義非內(nèi)聯(lián)函數(shù)時,模板的頭文件中也會放置所有的聲明和定義。這似乎違背了通常的頭文件規(guī)則:不要在分配存儲空間前放置任何東西,這條規(guī)則是為了防止在連接時的多重定義錯誤。但模板定義很特殊。由template.處理的任何東西都意味著編譯器在當(dāng)時不為它分配存儲空間,它一直出于等待狀態(tài)直到被一個模板實(shí)例告知。在編譯器和連接器的某一處,有一機(jī)制能去掉模板的多重定義,所以為了容易使用,幾乎總是在頭文件中放置全部的模板聲明和定義。為什么C+編譯器不能支持
13、對模板的分離式編譯劉未鵬(pongba)/文首先,C+標(biāo)準(zhǔn)中提到,一個編譯單元translation unit是指一個.cpp文件以及它所include的所有.h文件,.h文件里的代碼將會被擴(kuò)展到包含它的.cpp文件里,然后編譯器編譯該.cpp文件為一個.obj文件,后者擁有PEPortable Executable,即windows可執(zhí)行文件文件格式,并且本身包含的就已經(jīng)是二進(jìn)制碼,但是,不一定能夠執(zhí)行,因?yàn)椴⒉槐WC其中一定有main函數(shù)。當(dāng)編譯器將一個工程里的所有.cpp文件以分離的方式編譯完畢后,再由連接器(linker)進(jìn)行連接成為一個.exe文件。舉個例子:/-test.h-/voi
14、d f();/這里聲明一個函數(shù)f/-test.cpp-/#includetest.hvoid f()/do something/這里實(shí)現(xiàn)出test.h中聲明的f函數(shù)/-main.cpp-/#includetest.hint main()f();/調(diào)用f,f具有外部連接類型在這個例子中,test.cpp和main.cpp各被編譯成為不同的.obj文件姑且命名為test.obj和main.obj,在main.cpp中,調(diào)用了f函數(shù),然而當(dāng)編譯器編譯main.cpp時,它所僅僅知道的只是main.cpp中所包含的test.h文件中的一個關(guān)于void f();的聲明,所以,編譯器將這里的f看作外部連接
15、類型,即認(rèn)為它的函數(shù)實(shí)現(xiàn)代碼在另一個.obj文件中,本例也就是test.obj,也就是說,main.obj中實(shí)際沒有關(guān)于f函數(shù)的哪怕一行二進(jìn)制代碼,而這些代碼實(shí)際存在于test.cpp所編譯成的test.obj中。在main.obj中對f的調(diào)用只會生成一行call指令,像這樣:call fC+中這個名字當(dāng)然是經(jīng)過mangling處理過的在編譯時,這個call指令顯然是錯誤的,因?yàn)閙ain.obj中并無一行f的實(shí)現(xiàn)代碼。那怎么辦呢?這就是連接器的任務(wù),連接器負(fù)責(zé)在其它的.obj中本例為test.obj尋找f的實(shí)現(xiàn)代碼,找到以后將call f這個指令的調(diào)用地址換成實(shí)際的f的函數(shù)進(jìn)入點(diǎn)地址。需要注意
16、的是:連接器實(shí)際上將工程里的.obj連接成了一個.exe文件,而它最關(guān)鍵的任務(wù)就是上面說的,尋找一個外部連接符號在另一個.obj中的地址,然后替換原來的虛假地址。這個過程如果說的更深入就是:call f這行指令其實(shí)并不是這樣的,它實(shí)際上是所謂的stub,也就是一個jmp 0x23423這個地址可能是任意的,然而關(guān)鍵是這個地址上有一行指令來進(jìn)行真正的call f動作。也就是說,這個.obj文件里面所有對f的調(diào)用都jmp向同一個地址,在后者那兒才真正callf。這樣做的好處就是連接器修改地址時只要對后者的call XXX地址作改動就行了。但是,連接器是如何找到f的實(shí)際地址的呢在本例中這處于test
17、.obj中,因?yàn)?obj于.exe的格式都是一樣的,在這樣的文件中有一個符號導(dǎo)入表和符號導(dǎo)出表import table和export table其中將所有符號和它們的地址關(guān)聯(lián)起來。這樣連接器只要在test.obj的符號導(dǎo)出表中尋找符號f當(dāng)然C+對f作了mangling的地址就行了,然后作一些偏移量處理后因?yàn)槭菍蓚€.obj文件合并,當(dāng)然地址會有一定的偏移,這個連接器清楚寫入main.obj中的符號導(dǎo)入表中f所占有的那一項(xiàng)。這就是大概的過程。其中關(guān)鍵就是:編譯main.cpp時,編譯器不知道f的實(shí)現(xiàn),所有當(dāng)碰到對它的調(diào)用時只是給出一個指示,指示連接器應(yīng)該為它尋找f的實(shí)現(xiàn)體。這也就是說main.o
18、bj中沒有關(guān)于f的任何一行二進(jìn)制代碼。編譯test.cpp時,編譯器找到了f的實(shí)現(xiàn)。于是乎f的實(shí)現(xiàn)二進(jìn)制代碼出現(xiàn)在test.obj里。連接時,連接器在test.obj中找到f的實(shí)現(xiàn)代碼二進(jìn)制的地址通過符號導(dǎo)出表。然后將main.obj中懸而未決的call XXX地址改成f實(shí)際的地址。完成。然而,對于模板,你知道,模板函數(shù)的代碼其實(shí)并不能直接編譯成二進(jìn)制代碼,其中要有一個具現(xiàn)化的過程。舉個例子:/-main.cpp-/template class Tvoid f(T t)int main()/do something f(10);/call fint編譯器在這里決定給f一個f int的具現(xiàn)體/d
19、o other thing也就是說,如果你在main.cpp文件中沒有調(diào)用過f,f也就得不到具現(xiàn),從而main.obj中也就沒有關(guān)于f的任意一行二進(jìn)制代碼!如果你這樣調(diào)用了:f(10);/f int得以具現(xiàn)化出來f(10.0);/f double得以具現(xiàn)化出來這樣main.obj中也就有了f int,f double兩個函數(shù)的二進(jìn)制代碼段。以此類推。然而具現(xiàn)化要求編譯器知道模板的定義,不是嗎?看下面的例子:將模板和它的實(shí)現(xiàn)分離/-test.h-/template class Tclass Apublic:void f();/這里只是個聲明;/-test.cpp-/#includetest.ht
20、emplate class Tvoid AT:f()/模板的實(shí)現(xiàn),但注意:不是具現(xiàn)/do something/-main.cpp-/#includetest.hint main()A int a;a.f();/編譯器在這里并不知道A int:f的定義,因?yàn)樗辉趖est.h里面/于是編譯器只好寄希望于連接器,希望它能夠在其他.obj里面找到/A int:f的實(shí)現(xiàn)體,在本例中就是test.obj,然而,后者中真有A int:f的/二進(jìn)制代碼嗎?NO!因?yàn)镃+標(biāo)準(zhǔn)明確表示,當(dāng)一個模板不被用到的時/侯它就不該被具現(xiàn)出來,test.cpp中用到了A int:f了嗎?沒有!所以實(shí)/際上test.cpp編
21、譯出來的test.obj文件中關(guān)于A:f的一行二進(jìn)制代碼也沒有/于是連接器就傻眼了,只好給出一個連接錯誤/但是,如果在test.cpp中寫一個函數(shù),其中調(diào)用A int:f,則編譯器會將其/具現(xiàn)出來,因?yàn)樵谶@個點(diǎn)上test.cpp中,編譯器知道模板的定義,所以能/夠具現(xiàn)化,于是,test.obj的符號導(dǎo)出表中就有了A int:f這個符號的地/址,于是連接器就能夠完成任務(wù)。關(guān)鍵是:在分離式編譯的環(huán)境下,編譯器編譯某一個.cpp文件時并不知道另一個.cpp文件的存在,也不會去查找當(dāng)遇到未決符號時它會寄希望于連接器。這種模式在沒有模板的情況下運(yùn)行良好,但遇到模板時就傻眼了,因?yàn)槟0鍍H在需要的時候才會具
22、現(xiàn)化出來,所以,當(dāng)編譯器只看到模板的聲明時,它不能具現(xiàn)化該模板,只能創(chuàng)建一個具有外部連接的符號并期待連接器能夠?qū)⒎柕牡刂窙Q議出來。然而當(dāng)實(shí)現(xiàn)該模板的.cpp文件中沒有用到模板的具現(xiàn)體時,編譯器懶得去具現(xiàn),所以,整個工程的.obj中就找不到一行模板具現(xiàn)體的二進(jìn)制代碼,于是連接器也黔/C+模板代碼的組織方式-包含模式(Inclusion Model)選擇自sam1111的Blog關(guān)鍵字Template Inclusion Model出處C+Template:The Complete Guide說明:本文譯自C+Template:The Complete Guide一書的第6章中的部分內(nèi)容。最近看
23、到C+論壇上常有關(guān)于模板的包含模式的帖子,聯(lián)想到自己初學(xué)模板時,也為類似的問題困惑過,因此翻譯此文,希望對初學(xué)者有所幫助。模板代碼有幾種不同的組織方式,本文介紹其中最流行的一種方式:包含模式。鏈接錯誤大多數(shù)C/C+程序員向下面這樣組織他們的非模板代碼:類和其他類型全部放在頭文件中,這些頭文件具有.hpp(或者.H,.h,.hh,.hxx)擴(kuò)展名。對于全局變量和(非內(nèi)聯(lián))函數(shù),只有聲明放在頭文件中,而定義放在點(diǎn)C文件中,這些文件具有.cpp(或者.C,.c,.cc,.cxx)擴(kuò)展名。這種組織方式工作的很好:它使得在編程時可以方便地訪問所需的類型定義,并且避免了來自鏈接器的變量或函數(shù)重復(fù)定義的錯誤
24、。由于以上組織方式約定的影響,模板編程新手往往會犯一個同樣的錯誤。下面這一小段程序反映了這種錯誤。就像對待普通代碼那樣,我們在頭文件中定義模板:/basics/myfirst.hpp#ifndef MYFIRST_HPP#define MYFIRST_HPP/declaration of template template typename Tvoid print_typeof(T const&);#endif/MYFIRST_HPP print_typeof()聲明了一個簡單的輔助函數(shù)用來打印一些類型信息。函數(shù)的定義放在點(diǎn)C文件中:/basics/myfirst.cpp#include io
25、stream#include typeinfo#includemyfirst.hpp/implementation/definition of template template typename Tvoid print_typeof(T const&x)std:cout typeid(x).name()std:endl;這個例子使用typeid操作符來打印一個字符串,這個字符串描述了傳入的參數(shù)的類型信息。最后,我們在另外一個點(diǎn)C文件中使用我們的模板,在這個文件中模板聲明被#include:/basics/myfirstmain.cpp#includemyfirst.hpp/use of th
26、e template int main()double ice=3.0;print_typeof(ice);/call function template for type double大部分C+編譯器(Compiler)很可能會接受這個程序,沒有任何問題,但是鏈接器(Linker)大概會報告一個錯誤,指出缺少函數(shù)print_typeof()的定義。這個錯誤的原因在于,模板函數(shù)print_typeof()的定義還沒有被具現(xiàn)化(instantiate)。為了具現(xiàn)化一個模板,編譯器必須知道哪一個定義應(yīng)該被具現(xiàn)化,以及使用什么樣的模板參數(shù)來具現(xiàn)化。不幸的是,在前面的例子中,這兩組信息存在于分開編譯的
27、不同文件中。因此,當(dāng)我們的編譯器看到對print_typeof()的調(diào)用,但是沒有看到此函數(shù)為double類型具現(xiàn)化的定義時,它只是假設(shè)這樣的定義在別處提供,并且創(chuàng)建一個那個定義的引用(鏈接器使用此引用解析)。另一方面,當(dāng)編譯器處理myfirst.cpp時,該文件并沒有任何指示表明它必須為它所包含的特殊參數(shù)具現(xiàn)化模板定義。頭文件中的模板解決上面這個問題的通用解法是,采用與我們使用宏或者內(nèi)聯(lián)函數(shù)相同的方法:我們將模板的定義包含進(jìn)聲明模板的頭文件中。對于我們的例子,我們可以通過將#includemyfirst.cpp添加到myfirst.hpp文件尾部,或者在每一個使用我們的模板的點(diǎn)C文件中包含myfirst.cpp文件,來達(dá)到目的。當(dāng)然,還有第三種方法,就是刪掉myfirst.cpp文件,并重寫myfirst.hpp文件,使它包含所有的模板聲明與定義:/basics/myfirst2.hpp#ifndef MYFIRST_HPP#define MYFIRST_HPP#include iostream#include typeinfo/declaration of template tem
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2026年中遠(yuǎn)海運(yùn)航空貨運(yùn)代理有限公司重慶分公司招聘備考題庫含答案詳解
- 2026年華能內(nèi)蒙古東部能源有限公司招聘高校畢業(yè)生備考題庫及參考答案詳解
- 2026年威海市青少年宮公開招聘事業(yè)單位工作人員備考題庫完整參考答案詳解
- 2026年成都市雙流區(qū)川大江安小學(xué)教師招聘11人備考題庫及參考答案詳解一套
- 2026年開江縣人民醫(yī)院關(guān)于招聘編外工作人員備考題庫附答案詳解
- 2025-2026學(xué)年譯林版(三起)三年級上冊期末模擬測試英語試卷【含答案詳解】
- 銀行對公外匯內(nèi)控制度
- 殘聯(lián)財務(wù)內(nèi)控制度手冊
- 疫情期間內(nèi)控制度
- 城市檔案館內(nèi)控制度
- 2026年中國馬術(shù)行業(yè)發(fā)展現(xiàn)狀調(diào)查、競爭格局分析及未來前景預(yù)測報告
- 健康體檢重要異常結(jié)果管理專家共識2025
- TCNAS50-2025成人吞咽障礙患者口服給藥護(hù)理學(xué)習(xí)解讀課件
- 工程概算編制方案
- 可持續(xù)采購培訓(xùn)
- 2025至2030全球及中國供應(yīng)鏈的區(qū)塊鏈行業(yè)項(xiàng)目調(diào)研及市場前景預(yù)測評估報告
- 議論文寫作入門指導(dǎo)課件統(tǒng)編版高一語文必修上冊
- 北師大版初中英語七年級上冊期末復(fù)習(xí)試卷及答案
- 2025-2030中國特種陶瓷材料進(jìn)口替代空間與投資機(jī)會評估研究報告
- 脛骨平臺骨折課件
- 2025-2030中國建筑行業(yè)人才需求與培養(yǎng)戰(zhàn)略研究報告
評論
0/150
提交評論