若是說上一篇博文《模板名稱》是教人怎麼寫模板,那麼這一篇就是教人怎麼使用模板。html
閱讀此文以前要先閱讀上一篇博文理解什麼事受限名稱,什麼是依賴型名稱 → 函數
http://www.cnblogs.com/claruarius/p/4063795.htmlspa
模板實例化的複雜性在於:對於產生自模板的實體,它們的定義已經再也不侷限於源代碼中的單一位置。
1、理解兩個概念
(1)實例化:實例化在C++中一般指「根據類型建立一個對象」,可是在模板裏面,實例化是指使用
具體值替換模板實參,從模板中
產生普通類,函數或者成員函數的
過程。
(2)特化:這個過程最後得到的試題就是咱們所說的特化。
然而,實例化過程並非特化的惟一方式,還有顯式特化,經過引入一個template<>來實現,以下:
template<typename T1, typename T2>
class MyClass{};
template<>
class MyClass<std::string, float>{};
2、按需實例化(on-demand實例化)
若是(某個組件)
指望知道模板特化的大小,或者訪問該特化的成員,那麼整個定義就須要位於做用域中。
好比顯示的調用模板的成員,或者是包含隱式轉換
(1)顯示調用成員函數
template<typename T> class C; //前置聲明
C<int>* p = 0; //這裏只須要聲明就夠了
template<typename T>
class C{
public:
void f();
};
void g(C<int>& c){
c.f(); //此處須要知道整個模板的定義,由於編譯器要肯定f()是否是能夠被訪問到
}
(2)隱式類型轉換
C++重載規則要求:若是候選函數的參數是class類型,那麼該類型所對應的類就必須可見
template <typename T>
class C{
public:
C(int); //單參數隱式類型轉換
};
void candidate(C<double> const&); //①容許編譯器實例化該重載函數,但不是必須的,在VS2013中,就沒有實例化參數
void candidate(int){} //②
int main()
{
candidate(42); //編譯器不會選擇①處的聲明,
由於一個精確的匹配要優於顯式轉型所得到的匹配
return 0;
}
3、延遲實例化
編譯器只對確實須要的部分實例化。換句話說,編譯器會延遲模板的實例化。
(1)當隱式實例化類模板時,同時也實例化了該模板的每個成員函數的聲明,但並無實例化相應的定義。
可是有些狀況是不會延遲的,以下:
①類模板裏面包含有匿名的union,那麼,匿名的union成員同時也被實例化,
②虛函數,做爲實例化類模板的結果,許多編譯器實現都會實例化虛函數的定義,由於「實現虛函數調用機制的內部結構」要求虛函數的定義做爲連接實體的存在。
(2)實例化類模板與實例化缺省的函數調用實參是分開的。換句話說,只有函數確實使用了缺省的實參,纔會實例化該實參,若是這個函數不使用缺省的實參,那麼就不會實例化該缺省的實參,而是顯式使用實參來實例化。
template<typename T>
class Safe{};
template<int N>
class Danger{
typedef char Block[N]; //若是N<=0的話,將會出錯
};
template<typename T, int N>
class Tricky{
public:
virtual ~Tricky(){} //虛函數,並提供了定義
void no_body_here(Safe<T>=3); //該缺省實參是可疑的,但沒有被使用,不會被實例化,不會出錯
void inclass(){
Danger<N>no_boom_yet; //沒有被使用,不會被實例化,不會出錯
}
//void error(){ Danger<0> boom;} //若是沒有被註釋,會被要求給出這個類Danger<0>的完整定義,
//而實例化Danger<0>會出錯,即便沒有被使用,也不會被實例化,但仍然可以引起一個錯誤
//該錯誤是在泛模板處理中產生的
//void unsafe(T(*p)[N]); //若是 沒有註釋掉的話,此處實例化聲明的時候會出錯
T operator->();
//virtual Safe<T> suspect(); //虛函數,可是沒有提供定義,因此會引起一個連接期的錯誤,
//若是不註釋掉的話,連接器就會給出這類錯誤
struct Nested{
Danger<N> pfew; //由於沒有使用該結構,因此此處的沒有實例化htm
};
union{對象
int align;blog
Safe<T> anonymous;作用域
};
};
int main()
{
Tricky<int, 0> ok;
}
3、C++實例化模型
(1)兩階段查找
第一階段:使用普通查找規則(在適當狀況也會使用ADL)對模板進行解析,查找
非依賴型名稱。另外
非受限的依賴型名稱(諸如函數調用中的函數名稱,由於其具備一個依賴型實參)也會在這個階段查找,只不過查找不徹底,在實例化模板的時候還會再次進行查找。
第二階段:發生在模板被實例化的時候,咱們也稱此時發生的地點(或源代碼的某個位置)爲一個實例化點POI。
依賴型受限名稱就在此時查找。另外,
非受限的依賴型名稱在此階段也會再次執行ADL查找.
(2)POI(實例化點)
①對於指向
非類型(也就是函數╮( ̄▽ ̄")╭)特化的引用, C++把他的POI定義在「包含
這個引用的
定義或聲明
以後的最近名字空間域」。
class MyInt{
public:
MyInt(int i);
};
MyInt operator - (MyInt const);
bool operator >(MyInt const&, MyInt const&);
typedef MyInt int; //②
void e(){}//③
template <typename T>
void f(T i)
{
if(i>0){
g(-i); //①
e(); //非受限非依賴型名稱會在第一階段查找,因此若是要順利解析須要在此模板以前(能夠在③位置)定義函數e(),不然解析不經過
h(-i);//h是非受限依賴型名稱,因此函數h沒必要出如今模板f()以前,讓其可見(由於能夠經過ADL查找到名稱),以下。
}
}
//(1)
void g(Int)
//這就是那個定義或聲明
{
//(2)
f<Int>(42);
//這就是那個引用
//(3)
}
//(4)這就是那個「以後最近的名字空間域」,函數f<int>的一個特化會出如今這裏
void h(Int)
{
f<Int>(32);
}
【注意】①位置的名稱g, 是非受限依賴型名稱,由於他的參數是依賴型的, 因此會在第二階段查找
只是使用ADL就可以找到函數g(Int)其實也就是g(MyInt);若是將MyInt替換成int,即②處爲typedef int Int,那麼第二階段的查找關聯命名空間就會是空集,也就找不到函數g(Int)的聲明和定義。
②對於產生自模板的
類實例的引用,它的POI只能定義在「包含
這個實例引用的
定義或聲明以前的最近名字空間域」。
template<typename T>
class S{
public:
T m;
};
//(5) 這裏就是S的POI
unsigned long h()
//這就是那個定義或聲明
{
//(6)
return (unsigned long) sizeof(S<int>);
//這就是那個實例引用
//(7)
}
//(8)
4、顯式實例化
爲模板特化顯式的生成POI是可行的,咱們把這種特化的構形成爲顯式實例化指示符。從語法關鍵字上講,它有關鍵字template和後面的特化聲明組成,所聲明的特化就是即將有實例化得到的特化。
template<typename T>
void f(T) throw(T){}
下面有4個有效的顯式實例化實體
template void f<int>(int) throw(int);
template void f<>(int) throw(int);
template void f(int) throw(int);
//經過演繹得到
template void f(int);
//異常規範也能夠省略,若是沒有省略,異常規範必須匹配相應的模板
C++規定: 同一個程序中,每個特定的模板特化最多隻能存在一處顯式實例化。並且,若是摸個模板特化已經被顯式實例化(使用template),那麼就不能對其進行顯式特殊化(使用template<>)。