如何組織編寫(xiě)模板程序
前言
常遇到詢問(wèn)使用模板到底是否容易的問(wèn)題,我的回答是:“模板的使用是容易的,但組織編寫(xiě)卻不容易”??纯次覀儙缀趺刻於寄苡龅降哪0孱惏?,如STL, ATL, WTL, 以及Boost的模板類,都能體會(huì)到這樣的滋味:接口簡(jiǎn)單,操作復(fù)雜。
我在5年前開(kāi)始使用模板,那時(shí)我看到了MFC的容器類。直到去年我還沒(méi)有必要自己編寫(xiě)模板類??墒窃谖倚枰约壕帉?xiě)模板類時(shí),我首先遇到的事實(shí)卻是“傳統(tǒng)”編程方法(在*.h文件聲明,在*.cpp文件中定義)不能用于模板。于是我花費(fèi)一些時(shí)間來(lái)了解問(wèn)題所在及其解決方法。
本文對(duì)象是那些熟悉模板但還沒(méi)有很多編寫(xiě)模板經(jīng)驗(yàn)的程序員。本文只涉及模板類,未涉及模板函數(shù)。但論述的原則對(duì)于二者是一樣的。
問(wèn)題的產(chǎn)生
通過(guò)下例來(lái)說(shuō)明問(wèn)題。例如在array.h文件中有模板類array:
// array.h
template <typename T, int SIZE>
class array
{
T 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) const {return data_[i];}
void set_elem(int i, const T& value) {data_[i] = value;}
operator T*() {return data_;}
};
然后在main.cpp文件中的主函數(shù)中使用上述模板:
// main.cpp
#include "array.h"
int main(void)
{
array<int, 50> intArray;
intArray.set_elem(0, 2);
int firstElem = intArray.get_elem(0);
int* begin = intArray;
}
這時(shí)編譯和運(yùn)行都是正常的。程序先創(chuàng)建一個(gè)含有50個(gè)整數(shù)的數(shù)組,然后設(shè)置數(shù)組的第一個(gè)元素值為2,再讀取第一個(gè)元素值,最后將指針指向數(shù)組起點(diǎn)。
但如果用傳統(tǒng)編程方式來(lái)編寫(xiě)會(huì)發(fā)生什么事呢?我們來(lái)看看:
將array.h文件分裂成為array.h和array.cpp二個(gè)文件(main.cpp保持不變)
// array.h
template <typename T, int SIZE>
class array
{
T 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(int i, const T& value);
operator T*();
};
// array.cpp
#include "array.h"
template<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) const
{
return data_[i];
}
template<typename T, int SIZE> void array<T, SIZE>::set_elem(int i, const T& value)
{
data_[i] = value;
}
template<typename T, int SIZE> array<T, SIZE>::operator T*()
{
return data_;
}
編譯時(shí)會(huì)出現(xiàn)3個(gè)錯(cuò)誤。問(wèn)題出來(lái)了:
為什么錯(cuò)誤都出現(xiàn)在第一個(gè)地方?
為什么只有3個(gè)鏈接出錯(cuò)?array.cpp中有4個(gè)成員函數(shù)。
要回答上面的問(wèn)題,就要深入了解模板的實(shí)例化過(guò)程。
模板實(shí)例化
程序員在使用模板類時(shí)最常犯的錯(cuò)誤是將模板類視為某種數(shù)據(jù)類型。所謂類型參量化(parameterized types)這樣的術(shù)語(yǔ)導(dǎo)致了這種誤解。模板當(dāng)然不是數(shù)據(jù)類型,模板就是模板,恰如其名:
編譯器使用模板,通過(guò)更換模板參數(shù)來(lái)創(chuàng)建數(shù)據(jù)類型。這個(gè)過(guò)程就是模板實(shí)例化(Instantiation)。
從模板類創(chuàng)建得到的類型稱之為特例(specialization)。
模板實(shí)例化取決于編譯器能夠找到可用代碼來(lái)創(chuàng)建特例(稱之為實(shí)例化要素,
point of instantiation)。
要?jiǎng)?chuàng)建特例,編譯器不但要看到模板的聲明,還要看到模板的定義。
模板實(shí)例化過(guò)程是遲鈍的,即只能用函數(shù)的定義來(lái)實(shí)現(xiàn)實(shí)例化。
再回頭看上面的例子,可以知道array是一個(gè)模板,array<int, 50>是一個(gè)模板實(shí)例 - 一個(gè)類型。從array創(chuàng)建array<int, 50>的過(guò)程就是實(shí)例化過(guò)程。實(shí)例化要素體現(xiàn)在main.cpp文件中。如果按照傳統(tǒng)方式,編譯器在array.h文件中看到了模板的聲明,但沒(méi)有模板的定義,這樣編譯器就不能創(chuàng)建類型array<int, 50>。但這時(shí)并不出錯(cuò),因?yàn)榫幾g器認(rèn)為模板定義在其它文件中,就把問(wèn)題留給鏈接程序處理。
現(xiàn)在,編譯array.cpp時(shí)會(huì)發(fā)生什么問(wèn)題呢?編譯器可以解析模板定義并檢查語(yǔ)法,但不能生成成員函數(shù)的代碼。它無(wú)法生成代碼,因?yàn)橐纱a,需要知道模板參數(shù),即需要一個(gè)類型,而不是模板本身。
這樣,鏈接程序在main.cpp 或 array.cpp中都找不到array<int, 50>的定義,于是報(bào)出無(wú)定義成員的錯(cuò)誤。
至此,我們回答了第一個(gè)問(wèn)題。但還有第二個(gè)問(wèn)題,在array.cpp中有4個(gè)成員函數(shù),鏈接器為什么只報(bào)了3個(gè)錯(cuò)誤?回答是:實(shí)例化的惰性導(dǎo)致這種現(xiàn)象。在main.cpp中還沒(méi)有用上operator[],編譯器還沒(méi)有實(shí)例化它的定義。
解決方法
認(rèn)識(shí)了問(wèn)題,就能夠解決問(wèn)題:
在實(shí)例化要素中讓編譯器看到模板定義。
用另外的文件來(lái)顯式地實(shí)例化類型,這樣鏈接器就能看到該類型。
使用export關(guān)鍵字。
前二種方法通常稱為包含模式,第三種方法則稱為分離模式。
第一種方法意味著在使用模板的轉(zhuǎn)換文件中不但要包含模板聲明文件,還要包含模板定義文件。在上例中,就是第一個(gè)示例,在array.h中用行內(nèi)函數(shù)定義了所有的成員函數(shù)?;蛘咴趍ain.cpp文件中也包含進(jìn)array.cpp文件。這樣編譯器就能看到模板的聲明和定義,并由此生成array<int, 50>實(shí)例。這樣做的缺點(diǎn)是編譯文件會(huì)變得很大,顯然要降低編譯和鏈接速度。
第二種方法,通過(guò)顯式的模板實(shí)例化得到類型。最好將所有的顯式實(shí)例化過(guò)程安放在另外的文件中。在本例中,可以創(chuàng)建一個(gè)新文件templateinstantiations.cpp:
// templateinstantiations.cpp
#include "array.cpp"
template class array <int, 50>; // 顯式實(shí)例化
array<int, 50>類型不是在main.cpp中產(chǎn)生,而是在templateinstantiations.cpp中產(chǎn)生。這樣鏈接器就能夠找到它的定義。用這種方法,不會(huì)產(chǎn)生巨大的頭文件,加快編譯速度。而且頭文件本身也顯得更加“干凈”和更具有可讀性。但這個(gè)方法不能得到惰性實(shí)例化的好處,即它將顯式地生成所有的成員函數(shù)。另外還要維護(hù)templateinstantiations.cpp文件。
第三種方法是在模板定義中使用export關(guān)鍵字,剩下的事就讓編譯器去自行處理了。當(dāng)我在
Stroustrup的書(shū)中讀到export時(shí),感到非常興奮。但很快就發(fā)現(xiàn)VC 6.0不支持它,后來(lái)又發(fā)現(xiàn)根本沒(méi)有編譯器能夠支持這個(gè)關(guān)鍵字(第一個(gè)支持它的編譯器要在2002年底才問(wèn)世)。自那以后,我閱讀了不少關(guān)于export的文章,了解到它幾乎不能解決用包含模式能夠解決的問(wèn)題。欲知更多的export關(guān)鍵字,建議讀讀Herb Sutter撰寫(xiě)的文章。
結(jié)論
要開(kāi)發(fā)模板庫(kù),就要知道模板類不是所謂的"原始類型",要用其它的編程思路。本文目的不是要嚇唬那些想進(jìn)行模板編程的程序員。恰恰相反,是要提醒他們避免犯下開(kāi)始模板編程時(shí)都會(huì)出現(xiàn)的錯(cuò)誤。
//////////////////////////////
http://www.cnblogs.com/xgchang/archive/2004/11/12/63139.aspx
甚至是在定義非內(nèi)聯(lián)函數(shù)時(shí),模板的頭文件中也會(huì)放置所有的聲明和定義。這似乎違背了通常的頭文件規(guī)則:“不要在分配存儲(chǔ)空間前放置任何東西”,這條規(guī)則是為了防止在連接時(shí)的多重定義錯(cuò)誤。但模板定義很特殊。由template<...>處理的任何東西都意味著編譯器在當(dāng)時(shí)不為它分配存儲(chǔ)空間,它一直出于等待狀態(tài)直到被一個(gè)模板實(shí)例告知。在編譯器和連接器的某一處,有一機(jī)制能去掉模板的多重定義,所以為了容易使用,幾乎總是在頭文件中放置全部的模板聲明和定義。
為什么C++編譯器不能支持對(duì)模板的分離式編譯
劉未鵬(pongba) /文
首先,C++標(biāo)準(zhǔn)中提到,一個(gè)編譯單元[translation unit]是指一個(gè).cpp文件以及它所include的所有.h文件,.h文件里的代碼將會(huì)被擴(kuò)展到包含它的.cpp文件里,然后編譯器編譯該.cpp文件為一個(gè).obj文件,后者擁有PE[Portable Executable,即windows可執(zhí)行文件]文件格式,并且本身包含的就已經(jīng)是二進(jìn)制碼,但是,不一定能夠執(zhí)行,因?yàn)椴⒉槐WC其中一定有main函數(shù)。當(dāng)編譯器將一個(gè)工程里的所有.cpp文件以分離的方式編譯完畢后,再由連接器(linker)進(jìn)行連接成為一個(gè).exe文件。
舉個(gè)例子:
//---------------test.h-------------------//
void f();//這里聲明一個(gè)函數(shù)f
//---------------test.cpp--------------//
#include”test.h”
void f()
{
…//do something
} //這里實(shí)現(xiàn)出test.h中聲明的f函數(shù)
//---------------main.cpp--------------//
#include”test.h”
int main()
{
f(); //調(diào)用f,f具有外部連接類型
}
在這個(gè)例子中,test. cpp和main.cpp各被編譯成為不同的.obj文件[姑且命名為test.obj和main.obj],在main.cpp中,調(diào)用了f函數(shù),然而當(dāng)編譯器編譯main.cpp時(shí),它所僅僅知道的只是main.cpp中所包含的test.h文件中的一個(gè)關(guān)于void f();的聲明,所以,編譯器將這里的f看作外部連接類型,即認(rèn)為它的函數(shù)實(shí)現(xiàn)代碼在另一個(gè).obj文件中,本例也就是test.obj,也就是說(shuō),main.obj中實(shí)際沒(méi)有關(guān)于f函數(shù)的哪怕一行二進(jìn)制代碼,而這些代碼實(shí)際存在于test.cpp所編譯成的test.obj中。在main.obj中對(duì)f的調(diào)用只會(huì)生成一行call指令,像這樣:
call f [C++中這個(gè)名字當(dāng)然是經(jīng)過(guò)mangling[處理]過(guò)的]
在編譯時(shí),這個(gè)call指令顯然是錯(cuò)誤的,因?yàn)閙ain.obj中并無(wú)一行f的實(shí)現(xiàn)代碼。那怎么辦呢?這就是連接器的任務(wù),連接器負(fù)責(zé)在其它的.obj中[本例為test.obj]尋找f的實(shí)現(xiàn)代碼,找到以后將call f這個(gè)指令的調(diào)用地址換成實(shí)際的f的函數(shù)進(jìn)入點(diǎn)地址。需要注意的是:連接器實(shí)際上將工程里的.obj“連接”成了一個(gè).exe文件,而它最關(guān)鍵的任務(wù)就是上面說(shuō)的,尋找一個(gè)外部連接符號(hào)在另一個(gè).obj中的地址,然后替換原來(lái)的“虛假”地址。
這個(gè)過(guò)程如果說(shuō)的更深入就是:
call f這行指令其實(shí)并不是這樣的,它實(shí)際上是所謂的stub,也就是一個(gè)
jmp 0x23423[這個(gè)地址可能是任意的,然而關(guān)鍵是這個(gè)地址上有一行指令來(lái)進(jìn)行真正的call f動(dòng)作。也就是說(shuō),這個(gè).obj文件里面所有對(duì)f的調(diào)用都jmp向同一個(gè)地址,在后者那兒才真正”call”f。這樣做的好處就是連接器修改地址時(shí)只要對(duì)后者的call XXX地址作改動(dòng)就行了。但是,連接器是如何找到f的實(shí)際地址的呢[在本例中這處于test.obj中],因?yàn)?obj于.exe的格式都是一樣的,在這樣的文件中有一個(gè)符號(hào)導(dǎo)入表和符號(hào)導(dǎo)出表[import table和export table]其中將所有符號(hào)和它們的地址關(guān)聯(lián)起來(lái)。這樣連接器只要在test.obj的符號(hào)導(dǎo)出表中尋找符號(hào)f[當(dāng)然C++對(duì)f作了mangling]的地址就行了,然后作一些偏移量處理后[因?yàn)槭菍蓚€(gè).obj文件合并,當(dāng)然地址會(huì)有一定的偏移,這個(gè)連接器清楚]寫(xiě)入main.obj中的符號(hào)導(dǎo)入表中f所占有的那一項(xiàng)。
這就是大概的過(guò)程。其中關(guān)鍵就是:
編譯main.cpp時(shí),編譯器不知道f的實(shí)現(xiàn),所有當(dāng)碰到對(duì)它的調(diào)用時(shí)只是給出一個(gè)指示,指示連接器應(yīng)該為它尋找f的實(shí)現(xiàn)體。這也就是說(shuō)main.obj中沒(méi)有關(guān)于f的任何一行二進(jìn)制代碼。
編譯test.cpp時(shí),編譯器找到了f的實(shí)現(xiàn)。于是乎f的實(shí)現(xiàn)[二進(jìn)制代碼]出現(xiàn)在test.obj里。
連接時(shí),連接器在test.obj中找到f的實(shí)現(xiàn)代碼[二進(jìn)制]的地址[通過(guò)符號(hào)導(dǎo)出表]。然后將main.obj中懸而未決的call XXX地址改成f實(shí)際的地址。
完成。
然而,對(duì)于模板,你知道,模板函數(shù)的代碼其實(shí)并不能直接編譯成二進(jìn)制代碼,其中要有一個(gè)“具現(xiàn)化”的過(guò)程。舉個(gè)例子:
//----------main.cpp------//
template<class T>
void f(T t)
{}
int main()
{
…//do something
f(10); //call f<int> 編譯器在這里決定給f一個(gè)f<int>的具現(xiàn)體
…//do other thing
}
也就是說(shuō),如果你在main.cpp文件中沒(méi)有調(diào)用過(guò)f,f也就得不到具現(xiàn),從而main.obj中也就沒(méi)有關(guān)于f的任意一行二進(jìn)制代碼??!如果你這樣調(diào)用了:
f(10); //f<int>得以具現(xiàn)化出來(lái)
f(10.0); //f<double>得以具現(xiàn)化出來(lái)
這樣main.obj中也就有了f<int>,f<double>兩個(gè)函數(shù)的二進(jìn)制代碼段。以此類推。
然而具現(xiàn)化要求編譯器知道模板的定義,不是嗎?
看下面的例子:[將模板和它的實(shí)現(xiàn)分離]
//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //這里只是個(gè)聲明
};
//---------------test.cpp-------------//
#include”test.h”
template<class T>
void A<T>::f() //模板的實(shí)現(xiàn),但注意:不是具現(xiàn)
{
…//do something
}
//---------------main.cpp---------------//
#include”test.h”
int 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)一個(gè)模板不被用到的時(shí)
//侯它就不該被具現(xiàn)出來(lái),test.cpp中用到了A<int>::f了嗎?沒(méi)有??!所以實(shí)
//際上test.cpp編譯出來(lái)的test.obj文件中關(guān)于A::f的一行二進(jìn)制代碼也沒(méi)有
//于是連接器就傻眼了,只好給出一個(gè)連接錯(cuò)誤
//但是,如果在test.cpp中寫(xiě)一個(gè)函數(shù),其中調(diào)用A<int>::f,則編譯器會(huì)將其//具現(xiàn)出來(lái),因?yàn)樵谶@個(gè)點(diǎn)上[test.cpp中],編譯器知道模板的定義,所以能//夠具現(xiàn)化,于是,test.obj的符號(hào)導(dǎo)出表中就有了A<int>::f這個(gè)符號(hào)的地
//址,于是連接器就能夠完成任務(wù)。
}
關(guān)鍵是:在分離式編譯的環(huán)境下,編譯器編譯某一個(gè).cpp文件時(shí)并不知道另一個(gè).cpp文件的存在,也不會(huì)去查找[當(dāng)遇到未決符號(hào)時(shí)它會(huì)寄希望于連接器]。這種模式在沒(méi)有模板的情況下運(yùn)行良好,但遇到模板時(shí)就傻眼了,因?yàn)槟0鍍H在需要的時(shí)候才會(huì)具現(xiàn)化出來(lái),所以,當(dāng)編譯器只看到模板的聲明時(shí),它不能具現(xiàn)化該模板,只能創(chuàng)建一個(gè)具有外部連接的符號(hào)并期待連接器能夠?qū)⒎?hào)的地址決議出來(lái)。然而當(dāng)實(shí)現(xiàn)該模板的.cpp文件中沒(méi)有用到模板的具現(xiàn)體時(shí),編譯器懶得去具現(xiàn),所以,整個(gè)工程的.obj中就找不到一行模板具現(xiàn)體的二進(jìn)制代碼,于是連接器也黔
/////////////////////////////////
http://dev.csdn.net/develop/article/19/19587.shtm
C++模板代碼的組織方式 ——包含模式(Inclusion Model) 選擇自 sam1111 的 Blog
關(guān)鍵字 Template Inclusion Model
出處 C++ Template: The Complete Guide
說(shuō)明:本文譯自《C++ Template: The Complete Guide》一書(shū)的第6章中的部分內(nèi)容。最近看到C++論壇上常有關(guān)于模板的包含模式的帖子,聯(lián)想到自己初學(xué)模板時(shí),也為類似的問(wèn)題困惑過(guò),因此翻譯此文,希望對(duì)初學(xué)者有所幫助。
模板代碼有幾種不同的組織方式,本文介紹其中最流行的一種方式:包含模式。
鏈接錯(cuò)誤
大多數(shù)C/C++程序員向下面這樣組織他們的非模板代碼:
·類和其他類型全部放在頭文件中,這些頭文件具有.hpp(或者.H, .h, .hh, .hxx)擴(kuò)展名。
·對(duì)于全局變量和(非內(nèi)聯(lián))函數(shù),只有聲明放在頭文件中,而定義放在點(diǎn)C文件中,這些文件具有.cpp(或者.C, .c, .cc, .cxx)擴(kuò)展名。
這種組織方式工作的很好:它使得在編程時(shí)可以方便地訪問(wèn)所需的類型定義,并且避免了來(lái)自鏈接器的“變量或函數(shù)重復(fù)定義”的錯(cuò)誤。
由于以上組織方式約定的影響,模板編程新手往往會(huì)犯一個(gè)同樣的錯(cuò)誤。下面這一小段程序反映了這種錯(cuò)誤。就像對(duì)待“普通代碼”那樣,我們?cè)陬^文件中定義模板:
// basics/myfirst.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
// declaration of template
template <typename T>
void print_typeof (T const&);
#endif // MYFIRST_HPP
print_typeof()聲明了一個(gè)簡(jiǎn)單的輔助函數(shù)用來(lái)打印一些類型信息。函數(shù)的定義放在點(diǎn)C文件中:
// basics/myfirst.cpp
#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"
// implementation/definition of template
template <typename T>
void print_typeof (T const& x)
{
std::cout << typeid(x).name() << std::endl;
}
這個(gè)例子使用typeid操作符來(lái)打印一個(gè)字符串,這個(gè)字符串描述了傳入的參數(shù)的類型信息。
最后,我們?cè)诹硗庖粋€(gè)點(diǎn)C文件中使用我們的模板,在這個(gè)文件中模板聲明被#include:
// basics/myfirstmain.cpp
#include "myfirst.hpp"
// use of the template
int main()
{
double ice = 3.0;
print_typeof(ice); // call function template for type double
}
大部分C++編譯器(Compiler)很可能會(huì)接受這個(gè)程序,沒(méi)有任何問(wèn)題,但是鏈接器(Linker)大概會(huì)報(bào)告一個(gè)錯(cuò)誤,指出缺少函數(shù)print_typeof()的定義。
這個(gè)錯(cuò)誤的原因在于,模板函數(shù)print_typeof()的定義還沒(méi)有被具現(xiàn)化(instantiate)。為了具現(xiàn)化一個(gè)模板,編譯器必須知道哪一個(gè)定義應(yīng)該被具現(xiàn)化,以及使用什么樣的模板參數(shù)來(lái)具現(xiàn)化。不幸的是,在前面的例子中,這兩組信息存在于分開(kāi)編譯的不同文件中。因此,當(dāng)我們的編譯器看到對(duì)print_typeof()的調(diào)用,但是沒(méi)有看到此函數(shù)為double類型具現(xiàn)化的定義時(shí),它只是假設(shè)這樣的定義在別處提供,并且創(chuàng)建一個(gè)那個(gè)定義的引用(鏈接器使用此引用解析)。另一方面,當(dāng)編譯器處理myfirst.cpp時(shí),該文件并沒(méi)有任何指示表明它必須為它所包含的特殊參數(shù)具現(xiàn)化模板定義。
頭文件中的模板
解決上面這個(gè)問(wèn)題的通用解法是,采用與我們使用宏或者內(nèi)聯(lián)函數(shù)相同的方法:我們將模板的定義包含進(jìn)聲明模板的頭文件中。對(duì)于我們的例子,我們可以通過(guò)將#include "myfirst.cpp"添加到myfirst.hpp文件尾部,或者在每一個(gè)使用我們的模板的點(diǎn)C文件中包含myfirst.cpp文件,來(lái)達(dá)到目的。當(dāng)然,還有第三種方法,就是刪掉myfirst.cpp文件,并重寫(xiě)myfirst.hpp文件,使它包含所有的模板聲明與定義:
// basics/myfirst2.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
#include <iostream>
#include <typeinfo>
// declaration of template
template <typename T>
void print_typeof (T const&);
// implementation/definition of template
template <typename T>
void print_typeof (T const& x)
{
std::cout << typeid(x).name() << std::endl;
}
#endif // MYFIRST_HPP
這種組織模板代碼的方式就稱作包含模式。經(jīng)過(guò)這樣的調(diào)整,你會(huì)發(fā)現(xiàn)我們的程序已經(jīng)能夠正確編譯、鏈接、執(zhí)行了。
從這個(gè)方法中我們可以得到一些觀察結(jié)果。最值得注意的一點(diǎn)是,這個(gè)方法在相當(dāng)程度上增加了包含myfirst.hpp的開(kāi)銷。在這個(gè)例子中,這種開(kāi)銷并不是由模板定義自身的尺寸引起的,而是由這樣一個(gè)事實(shí)引起的,即我們必須包含我們的模板用到的頭文件,在這個(gè)例子中是<iostream>和<typeinfo>。你會(huì)發(fā)現(xiàn)這最終導(dǎo)致了成千上萬(wàn)行的代碼,因?yàn)橹T如<iostream>這樣的頭文件也包含了和我們類似的模板定義。
這在實(shí)踐中確實(shí)是一個(gè)問(wèn)題,因?yàn)樗黾恿司幾g器在編譯一個(gè)實(shí)際程序時(shí)所需的時(shí)間。我們因此會(huì)在以后的章節(jié)中驗(yàn)證其他一些可能的方法來(lái)解決這個(gè)問(wèn)題。但無(wú)論如何,現(xiàn)實(shí)世界中的程序花一小時(shí)來(lái)編譯鏈接已經(jīng)是快的了(我們?cè)?jīng)遇到過(guò)花費(fèi)數(shù)天時(shí)間來(lái)從源碼編譯的程序)。
拋開(kāi)編譯時(shí)間不談,我們強(qiáng)烈建議如果可能盡量按照包含模式組織模板代碼。
另一個(gè)觀察結(jié)果是,非內(nèi)聯(lián)模板函數(shù)與內(nèi)聯(lián)函數(shù)和宏的最重要的不同在于:它并不會(huì)在調(diào)用端展開(kāi)。相反,當(dāng)模板函數(shù)被具現(xiàn)化時(shí),會(huì)產(chǎn)生此函數(shù)的一個(gè)新的拷貝。由于這是一個(gè)自動(dòng)的過(guò)程,編譯器也許會(huì)在不同的文件中產(chǎn)生兩個(gè)相同的拷貝,從而引起鏈接器報(bào)告一個(gè)錯(cuò)誤。理論上,我們并不關(guān)心這一點(diǎn):這是編譯器設(shè)計(jì)者應(yīng)當(dāng)關(guān)心的事情。實(shí)際上,大多數(shù)時(shí)候一切都運(yùn)轉(zhuǎn)正常,我們根本就不用處理這種狀況。然而,對(duì)于那些需要?jiǎng)?chuàng)建自己的庫(kù)的大型項(xiàng)目,這個(gè)問(wèn)題偶爾會(huì)顯現(xiàn)出來(lái)。
最后,需要指出的是,在我們的例子中,應(yīng)用于普通模板函數(shù)的方法同樣適用于模板類的成員函數(shù)和靜態(tài)數(shù)據(jù)成員,以及模板成員函數(shù)。