C++Primer_Chap16_模板和泛型编程_List01_定义模板_笔记
? 面向?qū)ο笞兂?#xff08;OOP)和泛型編程都能處理在編寫(xiě)程序時(shí)不知道類型的情況。不同之處在于:
- OOP能處理類型在程序運(yùn)行之前都未知的情況
- 泛型編程中,在編譯時(shí)就能獲知類型。
函數(shù)模板
? 我們可以定義一個(gè)通用的函數(shù)模板(function template),一個(gè)函數(shù)模板就是一個(gè)公式,可生成針對(duì)特定類型的函數(shù)版本
template <typename T> int compare( const T &val1, const T &val2) {if( val1 < val2) return -1;if( val2 < val1)return 1;return 0; }? 模板定義以關(guān)鍵字template開(kāi)始,后跟一個(gè)模板參數(shù)列表(template parameter list),這是一個(gè)逗號(hào)分隔的一個(gè)或多個(gè)模板參數(shù)(template parameter)的列表,用<>包圍起來(lái)。
? 一般來(lái)說(shuō),可以將類型參數(shù)看做類型說(shuō)明符,就想內(nèi)置類型或類類型說(shuō)明符一樣使用。特別是,類型參數(shù)可以用來(lái)指定返回類型或函數(shù)的參數(shù),以及在函數(shù)體內(nèi)用于聲明變量或類型轉(zhuǎn)換。類型參數(shù)前必須使用關(guān)鍵字class或typename(在模板參數(shù)列表中,class和typename沒(méi)有什么不同):
template<typename T, class U> calc(const T&, const U&);非類型模板參數(shù)
? 除了定義類型參數(shù),還可以在模板中定義非類型參數(shù)(nontype parameter)。一個(gè)非類型參數(shù)表示一個(gè)值而非一個(gè)類型。通過(guò)一個(gè)特定的類型名而非關(guān)鍵字class或typename來(lái)指定非類型參數(shù)。
? 當(dāng)一個(gè)模板被實(shí)例化時(shí),非類型參數(shù)被一個(gè)用戶提供的或編譯器推斷出的值所代替。這些值必須是常量表達(dá)式,從而允許編譯器在編譯時(shí)時(shí)實(shí)例化模板。
template<unsigned N, unsigned M> int compare(const char (&p1)[N], const char(&p2)[M]) {return strcmp(p1, p2); }compare("hi", "mom"); int compare( const char (&p1)[3], const char (&p2)[4]);? 編譯器會(huì)使用字面常量的大小代替N和M,從而實(shí)例化模板。一個(gè)非類型參數(shù)可以是一個(gè)整型,或者是一個(gè)指向?qū)ο蠡蚝瘮?shù)類型的指針或(左值)引用。綁定到非類型整型參數(shù)的實(shí)參必須是一個(gè)常量表達(dá)式。綁定到指針或引用非類型參數(shù)的實(shí)參必須具有靜態(tài)的生存期。我們不能用一個(gè)普通(非static)局部變量或動(dòng)態(tài)變量作為指針或引用非類型模板參數(shù)的實(shí)參。指針參數(shù)也可以用nullptr或值為0的常量表達(dá)式來(lái)實(shí)例化。
inline和constexpr的函數(shù)模板
? inline和constexpr說(shuō)明符放在模板參數(shù)列表之后,返回類型之前:
template<typename T> inline T min(const T&, const T&);? 編寫(xiě)泛型代碼的兩個(gè)重要原則:
- 模板中的函數(shù)參數(shù)是const的引用(保證了函數(shù)可以用于不能拷貝的類型)
- 函數(shù)體中的條件判斷僅使用<比較運(yùn)算(降低compare對(duì)要處理的類型的要求,只需要有<,不必同時(shí)支持>)
? 實(shí)際上,如果真的關(guān)心類型無(wú)關(guān)和移植性,可能需要使用less來(lái)定義我們的函數(shù).(彌補(bǔ)原版本針對(duì)兩個(gè)指針,且兩個(gè)指針未指向相同的數(shù)據(jù)時(shí)代碼行為未定義的問(wèn)題)
template<typename T> int compare( const T &v1, const T &v2) {if( less<T>()(v1, v2))return -1;if( less<T>()(v2, v1))return 1;return 0; }? 模板程序應(yīng)該盡量減少對(duì)實(shí)參類型的要求。
模板編譯
? 當(dāng)編譯器遇到一個(gè)模板定義時(shí),它并不生成代碼。只有實(shí)例化出模板的一個(gè)特定版本時(shí),編譯器才會(huì)生成代碼。即當(dāng)我們使用而不是定義模板時(shí),編譯器才生成代碼,該特性會(huì)影響如何組織代碼以及錯(cuò)誤何時(shí)被檢測(cè)到。
? 為了生成一個(gè)實(shí)例化版本,編譯器需要掌握函數(shù)模板和類模板成員函數(shù)的定義。因此,與非模板代碼不同,模板的頭文件通常保護(hù)聲明和定義。
? 類模板
??類模板(class template)是用來(lái)生成類的狼途的。和函數(shù)模板不同之處是,編譯器不能為類模板推斷模板參數(shù)類型。為了使用類模板,我們必須在模板名后面的尖括號(hào)中提供額外信息——用來(lái)代替模板參數(shù)的模板實(shí)參列表
template <typename T> class Blob { public:typedef T value_type;typedef typename std::vector<T>::size_type size_type;Blob();Blob( std::initializer_list<T> i1);size_type size() const { return data->size(); }bool empty() const { return data->empty(); }void push_back( const T &t) { data->push_back(t); }void push_back( const T &&t) { data->push_back(std::move(t)); }void pop_back();T& back();T& operator[](size_type i);private:std::shared_ptr<std::vector<T>> data;void check(size_type i, const std::string &msg) const; };? 實(shí)例化類模板時(shí)必須提供額外的信息。我們現(xiàn)在知道浙西額額外信息是顯式模板實(shí)參(explicit template argument)列表,他們被綁定到模板參數(shù):
Blob<int> ia; Blob<int> ia2 = {0, 1, 2, 3, 4};?實(shí)例化會(huì)讓編譯器實(shí)例化出一個(gè)與下面定義等價(jià)的類:
template <> class Blob<int> { public:typedef typename std::vector<int>::size_type size_type;Blob();Blob( std::initializer_list<int> i1);size_type size() const { return data->size(); }bool empty() const { return data->empty(); }void push_back( const int &t) { data->push_back(t); }void push_back( const int &&t) { data->push_back(std::move(t)); }void pop_back();T& back();T& operator[](size_type i);private:std::shared_ptr<std::vector<int>> data;void check(size_type i, const std::string &msg) const; };? 一個(gè)類模板的每個(gè)實(shí)例都會(huì)形成一個(gè)獨(dú)立的類。類型Blob<string>和任何其他Blob類型都沒(méi)有關(guān)聯(lián),也不會(huì)對(duì)任何其他Blob類型的成員有特殊訪問(wèn)權(quán)限。
類模板的成員函數(shù)
? 類模板的成員函數(shù)具有和模板相同的模板參數(shù)。因此,定義在類模板之外的成員函數(shù)就必須以關(guān)鍵字template開(kāi)始,后接類模板參數(shù)列表。
ret-type StrBlob::member-name(parm-list) {}template <typename T> ret-type Blob<T>::member-name(parm-list) {}template <typename T> Blob<T>::Blob() : data( std::make_shared<std::vector<T>>()) {}template <typename T> Blob<T>::Blob( std:: initializer_list<T> i1) : data( std::make_shared<std::vector<T>>(i1)){}template <typename T> void Blob<T>::pop_back() {check( 0, "back on empty Blob");return data->pop_back(); }template <typename T> T& Blob<T>::back() {check( 0, "back on empty Blob");return data->back(); }template <typename T> T& Blob<T>::operator[](size_type i) {check(i, "subscript out of range");return (*data)[i]; }template <typename T> void Blob<T>::check( size_type i, const std::string &msg) const {if( i >= data->size() )throw std::out_of_range(msg); }? 如果一個(gè)成員函數(shù)沒(méi)有被使用,則它不會(huì)被實(shí)例化。成員函數(shù)只有在被用到時(shí)才進(jìn)行實(shí)例化,這一特性使得即使某種類型不能完全符合模板操作的要求,我們?nèi)匀荒苡迷擃愋蛯?shí)例化類。
在類代碼內(nèi)簡(jiǎn)化模板類名的使用
? 當(dāng)我們使用一個(gè)類模板類型時(shí)必須提供模板實(shí)參,但例外是在類模板自己的作用域中,我們可以直接使用模板名而不提供實(shí)參:
template <typename T> class BlobPtr { public:BlobPtr():curr(0) {}BlobPtr( Blob<T> &a, size_t sz = 0):wptr(a.data),curr(sz) {}T& operator*() const{auto p = check( curr, "dereference past end");return (*p)[curr];}BlobPtr& operator++();BlobPtr& operator--(); private:std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const; std::weak_ptr< std::vector<T>> wptr;std::size_t curr; };? ? 前置遞增/遞減返回BlobPtr&而不是BlobPtr<T>&,當(dāng)我們處于一個(gè)類模板的作用域中時(shí),編譯器處理模板自身引用時(shí)就好像我們已經(jīng)提供了與模板參數(shù)匹配的實(shí)參一樣。
? 在類模板外定義其成員時(shí),必須記住并不在類的作用域中,直到遇到類名才表示進(jìn)入類的作用域.
template <typename T> BlobPtr<T> BlobPtr<T>::operator++(int) {BlobPtr ret = *this;++*this;return ret; }類模板和友元
? 當(dāng)一個(gè)類包含一個(gè)有友元聲明時(shí),類和友元各自是否是模板是相互無(wú)關(guān)的。如果一個(gè)類模板包含一個(gè)非模板友元,則友元被授權(quán)可以訪問(wèn)所有模板示例。如果友元自身是模板,類可以授權(quán)給所有友元模板實(shí)例,也可以值授權(quán)給特定實(shí)例。
一對(duì)一友好關(guān)系
? 類模板與另一個(gè)(類或函數(shù))模板間友好關(guān)系的最常見(jiàn)的形式是建立對(duì)應(yīng)實(shí)例及其友元間的友好關(guān)系。為了引用(類或函數(shù))模板的一個(gè)特定實(shí)例,我們必須首先聲明模板自身(一個(gè)模板的聲明包括模板參數(shù)列表):
template <typename> class BlobPtr; template <typename> class Blob; template <typename T>bool operator==(const Blob<T>&, const Blob<T>&);template <typename T> class Blob{friend class BlobPtr<T>;friend bool operator==<T>(const Blob<T>&, const Blob<T>&); };? 前面三行的聲明是函數(shù)的參數(shù)聲明和Blob中的友元聲明所需要的。
? 友元聲明用Blob的模板形參作為它們自己的模板實(shí)參。因此,友好關(guān)系被限定在用相同類型實(shí)例化的Blob和BlobPtr相等運(yùn)算符之間。
Blob<char> ca; Blob<char> operator==<char>Blob<int> ia; Blob<int> operator==<int>通用和特定的模板友好關(guān)系
? 一個(gè)類也可以將另一個(gè)模板的每個(gè)實(shí)例都聲明為自己的友元,或者限定特定的示例為友元:
template <typename T> class Pal;class C{friend class Pal<C>; //用C實(shí)例化的Pal是C的一個(gè)友元//Pal2的所有示例都是C的友元,這種情況無(wú)需前置聲明template <typename T> friend class Pal2; };template <typename T> class C2 {//C2的每個(gè)實(shí)例將相同實(shí)例化的Pal聲明為友元friend class Pal<T>; //Pal的模板聲明必須在作用域之內(nèi)//Pal2的所有示例都是C2的每個(gè)實(shí)例的友元,不需要前置聲明template <typename X> friend class Pal2;//Pal3是一個(gè)非模板類,它是C2所有示例的友元friend class Pal3; };? 為了讓所有實(shí)例稱為友元,友元聲明中必須使用與類模板本身不同的模板參數(shù)。
令模板自己的類型參數(shù)稱為友元
? 可以將模板類型參數(shù)聲明成友元:
template <typename Type> class Bar{friend Type; };模板類型別名
? 我們可以定義一個(gè)typedef來(lái)引用實(shí)例化的類,但不能定義一個(gè)typedef引用一個(gè)模板。不過(guò),我們可以為類模板定義一個(gè)類型別名:
typedef Blob<string> StrBlob;template<typename T> using twin = pair<T, T>; twin<string> authors; //authors是一個(gè)pair<string, string> twin<int> win_loss; //win_loss是一個(gè)pair<int, int> twin<double> area;類模板的static成員
? 類模板的static數(shù)據(jù)成員必須有且僅有一個(gè)定義。類模板的每個(gè)實(shí)例都有一個(gè)獨(dú)有的static對(duì)象。與定義模板的成員函數(shù)類似,我們將static數(shù)據(jù)成員也定義為模板:
template <typename T> class Foo { public:static std::size_t count() {return ctr;} private:static std::size_t ctr; };template <typename T> size_t Foo<T>::ctr = 0;Foo<int> fi; auto ci = Foo<int>::count(); ct = fi.count(); ct = Foo::count(); //錯(cuò)誤模板參數(shù)
? 類似函數(shù)參數(shù)的名字,一個(gè)模板參數(shù)的名字沒(méi)有什么內(nèi)在含義,其可用范圍是在其什么之后,至模板聲明或定義結(jié)束之前(會(huì)隱藏外層作用域中聲明的相同名字)。參數(shù)名不能重用,但定義中的名字不必與聲明中的模板參數(shù)名字相同。
使用類的類型成員
? 假定T是一個(gè)模板類型參數(shù),當(dāng)編譯器遇到類似T::mem這樣的代碼時(shí),它不會(huì)知道m(xù)en是一個(gè)類型成員還是一個(gè)static數(shù)據(jù)成員,直到實(shí)例化。但為了處理模板,編譯器必須指定名字是否表示一個(gè)類型。
T::size_type * p; //是定義一個(gè)p的指針變量? //還是將一個(gè)名為size_type的static數(shù)據(jù)成員與p相乘? 默認(rèn)情況下,C++語(yǔ)言假定通過(guò)作用域訪問(wèn)符(::)訪問(wèn)的名字都不是類型。因此,如果希望使用一個(gè)模板類型參數(shù)的類型名字,必須顯式告訴編譯器該名字是一個(gè)類型(通過(guò)使用關(guān)鍵字typename來(lái)實(shí)現(xiàn)):
template <typename T> typename T::value_type top(const T& c) { if( !c.empty())return c.back();elsereturn typename T::value_type(); }? 當(dāng)我們希望通知編譯器一個(gè)名字表示類型時(shí),必須使用關(guān)鍵字typename,而不能使用class
默認(rèn)模板實(shí)參
? 我們可以提供默認(rèn)模板實(shí)參(default template argument)。在C++11標(biāo)準(zhǔn)后,可以為函數(shù)和類模板提供默認(rèn)實(shí)參。
template <typename T, typename F = less<T>> int compare(const T &v1, const T &v2, F f = F()) {if( f(v1, v2))return -1;if( f(v2, v1))return 1;return 0; }模板默認(rèn)實(shí)參和類模板
無(wú)論何時(shí)使用一個(gè)類模板,都必須在模板名之后接上尖括號(hào)。尖括號(hào)指出類必須從一個(gè)模板實(shí)例化而來(lái)。特別,如果一個(gè)類模板為其所有模板參數(shù)都提供了默認(rèn)實(shí)參,且希望使用這些默認(rèn)實(shí)參,就必須在模板名之后跟一個(gè)空尖括號(hào)對(duì):
template<class T = int> class Numbers{//默認(rèn)T為int public:Numbers(T v = 0) : val(v) {} private:T val; };Numbers<long double> lots_of_precision; Numbers<> average_precision;成員模板
? 一個(gè)類(無(wú)論是普通類還是類模板)可以包含本身是模板的成員函數(shù)。這種成員稱為成員模板(member template)。成員模板不是虛函數(shù)。
? 重載的函數(shù)調(diào)用運(yùn)算符希望刪除器適用于任何類型,所以將調(diào)用運(yùn)算符定義為一個(gè)模板:
class DebugDelete { public:DebugDelete(std::ostream &os = std::cerr ) : os(s) {}template <typename T> void operator()(T *p) const{ os << "deleting unique_ptr" << std::endl;delete p; } private:std::ostream &os; };double *p = new double; DebugDelete d; //調(diào)用DebugDelete::operator()(double *),釋放p d(p);int *ip = new int; //在一個(gè)臨時(shí)DebugDelete對(duì)象上調(diào)用operator()(int *) DebugDelete()(ip);? 由于調(diào)用一個(gè)DebugDelete對(duì)象會(huì)delete其給定指針,我們可以將其用作unique_ptr的刪除器。為了重載unique_ptr的刪除器,我們?cè)诩饫ㄌ?hào)內(nèi)給出了刪除器類型,并提供一個(gè)這種類型的對(duì)象給unique_ptr的構(gòu)造函數(shù):
unique_ptr<int, DebugDelete> p(new int, DebugDelete()); unique_ptr<string, DebugDelete> sp(new string, DebugDelete());類模板的成員模板
? 對(duì)于類模板,也可以為其定義成員模板:
template <typename T> class Blob {template <typename IT> Blob(It b, It e);};? 與類模板的普通成員函數(shù)不同,成員模板是函數(shù)模板。當(dāng)在類模板外定義一個(gè)成員模板時(shí),必須同時(shí)為類模板和成員模板提供模板參數(shù)列表。類模板的參數(shù)列表在前,后跟成員自己的模板參數(shù)列表:
template <typename T> template <typename IT>Blob<T>::Blob(IT b, IT e) : data(std::make_shared<std::vector<T>>(b, e) ) { }實(shí)例化和成員模板
? 為了實(shí)例化一個(gè)類模板的成員模板,我們必須同時(shí)提供類和函數(shù)模板的實(shí)參。與普通函數(shù)模板相同,編譯器通常根據(jù)傳遞給成員模板的函數(shù)實(shí)參來(lái)推斷它的模板類型:
int ia[] = {0,1,2}; vector<long> vi = {0, 1, 2, 3}; list<const char*> w = {"now", "is", "the", "time"};//實(shí)例化Blob<int>類及接受兩個(gè)int*參數(shù)的構(gòu)造函數(shù) Blob<int> a1(begin(ia), end(ia));//實(shí)例化Blob<int>類及接受兩個(gè)vector<long>::iterator的構(gòu)造函數(shù) Blob<int> a2(vi.begin(), vi.end());//實(shí)例化Blob<string>類及接受兩個(gè)list<const char*>::iterator的構(gòu)造函數(shù) Blob<string> a2(w.begin(), w.end());控制實(shí)例化
當(dāng)模板被使用時(shí)才進(jìn)行實(shí)例化這特性意味著相同的實(shí)例可能會(huì)出現(xiàn)在多個(gè)對(duì)象文件中(.o)。在大系統(tǒng)中,在多個(gè)文件中實(shí)例化化相同模板的額外開(kāi)銷可能非常嚴(yán)重。可以通過(guò)顯示實(shí)例化(explicit instantiation)來(lái)避免這種開(kāi)銷:
extern template declaration; //實(shí)例化聲明 template declaration; //實(shí)例化定義? declaration是一個(gè)類或函數(shù)聲明,其中所有模板參數(shù)已被替換成模板實(shí)參,例如:
extern template class Blob<string>; //聲明 template int compare(const int&, const int&); //定義? 由于編譯器你在使用一個(gè)模板時(shí)自動(dòng)對(duì)其實(shí)例化。因此extern聲明必須出現(xiàn)在任何使用此實(shí)例化版本的代碼之前。
// Application.cc //這些模板類型必須在程序其他位置進(jìn)行實(shí)例化 extern template class Blob<string>; extern template int compare(const int&, const int&); Blob<string> sa1, sa2; //實(shí)例化會(huì)出現(xiàn)在其他位置//Blob<int>及其接受initializer_list的構(gòu)造函數(shù)在本文件中實(shí)例化 Blob<int> a1 = {0, 1, 2, 3, 4, 5, 6}; Blob<int> a2(a1); //拷貝構(gòu)造函數(shù)在本文件中實(shí)例化 int i = compare(a1[0], a2[0]); //實(shí)例化出現(xiàn)在其他位置? ?文件Application.o將包含Blob<int>的實(shí)例及其接受initializer_list參數(shù)的構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)的實(shí)例。而compare<int>函數(shù)和Blob<string>類將不再本文件中進(jìn)行實(shí)例化。這些模板的定義必須出現(xiàn)在程序的其他文件中:
//templateBuild.cc //實(shí)例化文件必須為每個(gè)在其他文件中聲明為extern的類型和函數(shù)提供一個(gè)(非extern)的定義 template int compare(const int&, const int&); template class Blob<string>;? ?當(dāng)編譯器遇到一個(gè)實(shí)例化定義(與聲明相對(duì))時(shí),它為其生存代碼。因此templateBuild.o將包含compare的int實(shí)例化版本的定義和Blob<string>類的定義。
? 對(duì)于每個(gè)實(shí)例化聲明,在程序中某個(gè)位置必須有其顯式的實(shí)例化定義。實(shí)例化定義會(huì)實(shí)例化所有成員:一個(gè)類模板的實(shí)例化定義會(huì)實(shí)例化該模板的所有成員,包括內(nèi)聯(lián)的成員函數(shù)。當(dāng)編譯器遇到一個(gè)實(shí)例化定義時(shí),它不了解程序使用那些成員函數(shù)。因此與處理類模板的普通實(shí)例化不同,編譯器會(huì)實(shí)例化該類的所有成員。因此,我們用來(lái)顯示實(shí)例化一個(gè)類模板的類型,必須能用于模板的所有的成員。
總結(jié)
以上是生活随笔為你收集整理的C++Primer_Chap16_模板和泛型编程_List01_定义模板_笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android6.0 Bluetooth
- 下一篇: 【洛谷】P1957 口算练习题【C++】