模板(Template)
最近閱讀google chromium base container stack_container代碼,深刻感覺到基礎知識不扎實。
// Casts the buffer in its right type.
T* stack_buffer() { return stack_buffer_.template data_as<T>(); }
const T* stack_buffer() const {
? return stack_buffer_.template data_as<T>();
}
關于.template的用法,維基百科關于Template描述中template關鍵字有詳細說明。
模板(Template)指C++程序設計語言中的函數模板與類模板[1],是一種參數化類型機制,大體對應于java和C#中的泛型,但也有一些功能上的顯著差異(C++模板支持后兩者沒有明確對應的模板模板參數和模板非類型參數,但不支持Java的通配符以及C#的泛型類型約束)。模板是C++的泛型編程中不可缺少的一部分。
模板是C++程序員絕佳的武器,特別是結合了多重繼承與運算符重載之后。C++的標準函數庫提供的許多有用的函數大多結合了模板的概念,如STL以及iostream。
目錄
- 1 語法
- 1.1 模板的聲明與定義
- 1.1.1 模板的非類型形參
- 1.1.2 模板的模板參數
- 1.1.3 模板參數的默認值
- 1.1.4 變量模板
- 1.2 模板的使用
- 1.1 模板的聲明與定義
- 2 示例
- 2.1 函數模板
- 2.2 類模板
- 2.3 模板的嵌套:成員模板
- 3 依賴名字與typename關鍵字
- 4 template關鍵字
- 5 別名模板
- 6 模板實例化
- 7 參考文獻
語法[目錄]
模板的聲明與定義[目錄]
模板定義以關鍵字template開始,后接模板形參表(template parameter list),模板形參表是用尖括號括住的一個或者多個模板形參的列表,形參之間以逗號分隔。模板形參可以是表示類型的類型形參(type parameter),也可以是表示常量表達式的非類型形參(non-type parameter)。非類型形參跟在類型說明符之后聲明。類型形參跟在關鍵字class或typename之后聲明。模板形參可以給出默認值(default arguments for template parameters)。
模板的非類型形參[目錄]
模板的非類型形參(template non-type parameter)允許為下述形式:
- 整型或枚舉型
- 到對象的指針或函數指針
- 到對象的引用或函數引用
- 成員指針
模板的非類型參數被聲明為數組或函數的,將被轉換為指針或函數指針。例如:
template<int a[4]> struct A { };template<int f(int)> struct B { };int i;int g(int) { return 0;}A<&i> x;B<&g> y;模板的非類型形參允許用const或volatile限定(而模板的類型形參是不允許cv限定的)。模板的非類型形參是不允許聲明為浮點型、class類型、void型。
模板的模板參數[目錄]
主條目:模板的模板參數類模板的模板參數允許是另外一個類模板,這稱為模板的模板參數(template template parameter),也譯作“模板參數模板”。函數模板不允許有模板的模板參數。例如:
template<template <class T> class X> class A { }; //類模板A的第二個參數是另外一個類模板X template<class T> class B { }; A<B> a; //模板A的實際使用。其中的B是模板的模板實參(template template argument)模板參數的默認值[目錄]
模板形參可以給出默認值(default arguments for template parameters)。如果一個模板參數給出了默認值,那么模板形參列表中在其后聲明的模板參數都應該給出默認值。例如:
template<class T = char, class U, class V = int> class X { }; //編譯出錯,或者給出U的默認值,或者不給出T的默認值一個模板的各次聲明給出的模板參數的默認值可以累積其效果。例如:
template<class T, class U = int> class A; template<class T = float, class U> class A; template<class T, class U> class A {public:T x;U y; }; A<> a; //a.x is float, and the type of a.y is int但是如果交換本示例第一行與第二行的次序,將編譯報錯。因為如果第一個模板參數T有了默認值,此時編譯器必須已經知道其后的第二個模板參數U的默認值。
在同一個作用于(scope)中,不能對同一個模板的同一個參數多次聲明其默認值。例如:
template<class T = char> class X; template<class T = char> class X { };//編譯報錯。如果在本行中不給出模板參數T的默認值將編譯通過模板參數的作用域為從其聲明之處至該模板的定義結束之處。因此可以使用一個模板參數作為其后聲明的其他模板參數的一部分或默認值。例如:
template<class V, V obj> class C; template<class T, class U = T> class D { };變量模板[目錄]
變量模板(variable template)是C++14引入的新的一個種類的模板。可用于在命名空間作用域聲明一個變量。例如:
template<class T> constexpr T pi = T(3.1415926535897932385); // variable template template<class T> T circular_area(T r) // function template {return pi<T> * r * r; // pi<T> is a variable template instantiation }可以在類作用域聲明一個靜態數據成員:
struct matrix_constants {template<class T>using pauli = hermitian_matrix<T, 2>; // alias templatetemplate<class T> static constexpr pauli<T> sigma1 = { { 0, 1 }, { 1, 0 } }; // static data member templatetemplate<class T> static constexpr pauli<T> sigma2 = { { 0, -1i }, { 1i, 0 } };template<class T> static constexpr pauli<T> sigma3 = { { 1, 0 }, { 0, -1 } };};
類的靜態數據成員模板,也可以用類模板的非模板數據成員來實現:
struct limits {template<typename T>static const T min; // declaration of a static data member template }; template<typename T> const T limits::min = { }; // definition of a static data member template template<class T> class X {static T s; // declaration of a non-template static data member of a class template }; template<class T> T X<T>::s = 0; // definition of a non-template data member of a class template變量模板不能用作模板的模板參數(template template arguments)。
模板的使用[目錄]
使用模板時,可以在模板名字后面顯式給出用尖括號括住的模板實參列表(template argument list)。對模板函數或類的模板成員函數,也可不顯式給出模板實參,而是由編譯器根據函數調用的上下文推導出模板實參,這稱為模板參數推導。
如果模板參數使用其默認值,則在模板實參列表中可以忽略它。如果所有的模板參數都使用了默認值,模板實參列表為空,但仍然必須寫出成對的尖括號。例如:
template<class T = int> class X { }; X<> a; //編譯通過 X b; //編譯報錯對于作為類型的模板實參,不允許是局部類型(local type)、無鏈接性的類型(type with no linkage)、無名類型(unnamed type)或包括了這三種情形的復合類型。[2]但C++11以及允許本地類型作為模板實參。
示例[目錄]
函數模板[目錄]
以下以取最大值的函數模板maximum為例。此函數在編譯時會自動產生對應參數類型的代碼,而不用顯式聲明。
#include <iostream>template <typename T> inline const T& maximum(const T& x,const T& y) {if(y > x){return y;}else{return x;} }int main(void) {using namespace std;int a=3,b=7;float x=3.0,y=7.0;//Calling template functionstd::cout << maximum<int>(a,b) << std::endl; //輸出 7std::cout << maximum(a, b) << std::endl; //自動補充類型聲明std::cout << maximum<double>(x,y) << std::endl; //輸出 7return 0; }類模板[目錄]
以下以將組件指針的操作,封裝成類別模板ComPtr為例。 #pragma oncetemplate <typename Ty> class ComPtr { protected:Ty* m_ptr;public:ComPtr(){m_ptr = NULL;}ComPtr(const ComPtr& rhs){m_ptr = NULL;SetComPtr(rhs.m_ptr);}ComPtr(Ty* p){m_ptr = NULL;SetComPtr(p);}~ComPtr(){Release();}const ComPtr& operator=(const ComPtr& rhs){SetComPtr(rhs.m_ptr);return *this;}Ty* operator=(Ty* p){SetComPtr(p);return p;}operator Ty* (){return m_ptr;}Ty* operator->(){return m_ptr;}operator Ty** (){Release();return &m_ptr;}operator void** (){Release();return (void**)&m_ptr;}bool IsEmpty(){return (m_ptr == NULL);}void SetComPtr(Ty* p){Release();m_ptr = p;if (m_ptr){m_ptr->AddRef();}}void Release(){if (m_ptr){m_ptr->Release();m_ptr = NULL;}} };模板的嵌套:成員模板[目錄]
對于類中的模板成員函數、嵌套的成員類模板,可以在封閉類的內部或外部定義它們。當模板成員函數、嵌套類模板在其封閉類的外部定義時,必須以封閉類模板的模板參數(如果它們也是模板類)和成員模板的模板參數開頭。[1]如下例:
template <typename C> class myc{public:template <typename S> C foo(S s); };//下行需要給出外部類與內部嵌套類的模板形參列表: template<typename C> template <typename S> C myc<C>::foo(S s){ C var; return var; }int main() { float f; myc<int> v1; v1.foo(f); }C++標準規定:如果外圍的類模板沒有特例化,里面的成員模板就不能特例化[3]。例如:
template <class T1> class A {template<class T2> class B {template<class T3> void mf1(T3);void mf2();}; };template <> template <class X>class A<int>::B {template <class T> void mf1(T);};template <> template <> template<class T>void A<int>::B<double>::mf1(T t) { }template <class Y> template <>void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but its enclosing class template A is not依賴名字與typename關鍵字[目錄]
一個模板中的依賴于一個模板參數(template parameter)的名字被稱為依賴名字 (dependent name)。當一個依賴名字嵌套在一個類的內部時,稱為嵌套依賴名字(nested dependent name)。一個不依賴于任何模板參數的名字,稱為非依賴名字(non-dependent name)。[4]
編譯器在處理模板定義時,可能并不確定依賴名字表示一個類型,還是嵌套類的成員,還是類的靜態成員。C++標準規定:如果解析器在一個模板中遇到一個嵌套依賴名字,它假定那個名字不是一個類型,除非顯式用typename關鍵字前置修飾該名字。[5]
typename關鍵字有兩個用途:
在下述情形,對嵌套依賴類型名字不需要前置修飾typename關鍵字:[6]
- 派生類聲明的基類列表中的基類標識符;
- 成員初始化列表中的基類標識符;
- 用class、struct、enum等關鍵字開始的類型標識符
因為它們的上下文已經指出這些標識符就是作為類型的名字。例如:
template <class T> class A: public T::Nested { //基類列表中的T::Nestedpublic:A(int x) : T::Nested(x) {}; //成員初始化列表中的T::Nestedstruct T::type1 m; //已經有了struct關鍵字的T::type1 };class B{public:class Nested{public:Nested(int x){};};typedef struct {int x;} type1; };int main() {A<B> a(101);return 0; }template關鍵字[目錄]
template關鍵字有兩個用途:
別名模板[目錄]
別名模板(aliase template)是C++11引入的技術。在C++03標準中,可以用typedef給全特化模板定義新的類型名。但是不允許用typedef施加于偏特化模板上。例如:
template <typename First, typename Second, int Third> class SomeType;template <typename Second> typedef SomeType<OtherType, Second, 5> TypedefName; // Illegal in C++03C++11增加了給偏特化模板增加別名的功能,例如:
template <typename First, typename Second, int Third> class SomeType;template <typename Second> using TypedefName = SomeType<OtherType, Second, 5>;using在C++11中也可用于其他的類型別名的聲明:
typedef void (*FunctionType)(double); // Old style using FunctionType1 = void (*)(double); // New introduced syntax模板實例化[目錄]
模板實例化(template instantiation)是指在編譯或鏈接時生成函數模板或類模板的具體實例源代碼。ISO C++定義了兩種模板實例化方法:隱式實例化(當使用實例化的模板時自動地在當前代碼單元之前插入模板的實例化代碼)、顯式實例化(直接聲明模板實例化)。在C++語言的不同實現中,模板編譯模式(模板初始化的方法)大致可分為三種:
- Borland模型(包含模板編譯模式):編譯器生成每個編譯單元中遇到的所有的模板實例,并存放在相應的目標文件中;鏈接器合并相同的模板實例,生成可執行文件。為了在每次模板實例化時模板的定義都是可見的,模板的聲明與定義放在同一個.h文件中。這種方法的優點是鏈接器只需要處理目標文件;這種方法的缺點是由于模板實例被重復編譯,編譯時間被加長了,而且不能使用系統的鏈接器,需重新設計鏈接器。
- Cfront/查詢模型(分離(Separation)模板編譯模式):AT&T公司的C++編譯器Cfront為解決模板實例化問題,增加了一個模板倉庫,用以存放模板實例的代碼并可被自動維護。當生成一個目標文件時,編譯器把遇到的模板定義與當前可生成的模板實例存放到模板倉庫中。鏈接時,鏈接器的包裝程序(wrapper)首先調用編譯器生成所有需要的且不在模板倉庫中的模板實例。這種方法的優點是編譯速度得到了優化,而且可以直接使用系統的鏈接器;這種方法的缺點是復雜度大大增加,更容易出錯。使用這種模型的源程序通常把模板聲明與非內聯的模板成員分別放在.h文件與模板定義文件中,后者單獨編譯。
- 混合(迭代)模型:g++目前是基于Borland模型完成模板實例化。g++未來將實現混合模型的模板實例化,即編譯器把編譯單元中的模板定義與遇到的當前可實現的模板實例存放在相應的目標文件中;鏈接器的包裝程序(wrapper)調用編譯器生成所需的目前還沒有實例化的模板實例;鏈接器合并所有相同的模板實例。使用這種模型的源程序通常把模板聲明與非內聯的模板成員分別放在.h文件與模板定義文件中,后者單獨編譯。
ISO C++標準規定,如果隱式實例化模板,則模板的成員函數一直到引用時才被實例化;如果顯式實例化模板,則模板所有成員立即都被實例化,所以模板的聲明與定義在此處都應該是可見的,而且在其它程序文本文件使用了這個模板實例時用編譯器選項抑制模板隱式實例化,或者模板的定義部分是不可見的,或者使用template<> type FUN_NAME(type list)的語句聲明模板的特化但不實例化。
g++的模板實例化,目前分為三種方式:[7]
- 不指定任何特殊的編譯器參數:按Borland模型寫的源代碼能正常完成模板實例化,但每個編譯單元將包含所有它用到的模板實例,導致在大的程序中無法接受的代碼冗余。需要用GNU的鏈接器刪除各個目標文件中冗余的模板實例,不能使用操作系統提供的鏈接器。
- 使用-fno-implicit-templates編譯選項:在生成目標文件時完全禁止隱式的模板實例化,所有模板實例都顯式的寫出來,可以存放在一個單獨的源文件中;也可以存放在各個模板定義文件中。如果一個很大的源文件中使用了各個模板實例,這個源文件不用-fno-implicit-templates選項編譯,就可以自動隱式的生成所需要的模板實例。在生成庫文件時這個編譯選項特別有用。
- 使用-frepo編譯選項:在生成每個目標文件時,把需要用到的當前可生成的模板實例存放在相應的.rpo文件中。鏈接器包裝程序(wrapper)—collect2將刪除.rpo文件中冗余的模板實例并且修改相應的.rpo文件,使得編譯器可以利用.rpo文件知道在那里正確放置、引用模板實例,并重新編譯生成受影響的目標文件。由操作系統的通用的鏈接器生成可執行文件。這對Borland模型是很好的模板實例化方法。對于使用Cfront模型的軟件,需要修改源代碼,在模板頭文件的末尾加上#include <tmethods.cc>。不過MinGW中不包含鏈接器包裝程序collect2,故不使用此方法。對于庫(library),建議使用顯式實例化方法。
- 另外,g++擴展了ISO C++標準,用extern關鍵字指出模板實例在其它編譯單元中顯式聲明(這已經被C++11標準接受);用inline關鍵字實例化編譯器支持的數據(如類的虛表)但不實例化模板成員;用static關鍵字實例化模板的靜態數據成員但不實例化其它非靜態的模板成員。
- g++不支持模板實例化的export關鍵字(此關鍵字的這個用法已在C++11標準里被取消)。
VC++7.0中必須類模板實例化只有Borland模型;函數模板一般隱式實例化,自5.0版以后也可顯式實例化。
參考文獻[目錄]
轉載于:https://www.cnblogs.com/liaokang/p/5663227.html
總結
以上是生活随笔為你收集整理的模板(Template)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到自己在笑怎么回事
- 下一篇: 近期学习清单