1. 程式人生 > >C/C++基礎--模板與泛型程式設計

C/C++基礎--模板與泛型程式設計

  • 模板引數

函式模板,編譯器根據實參來為我們推斷模板實參。
模板中可以定義非型別引數,表示一個值而非一個型別,這些值必須是常量表達式,從而允許編譯器在編譯時例項化模板。
非型別引數可以是整型,或者一個指向物件或函式的指標或(左值)引用。繫結到前者的實參必須是常量表達式,繫結到後者的必須具有靜態生存期。

  • 泛型程式碼兩個原則

1模板中的函式引數是const的引用
2函式體中的條件判斷僅適用<比較運算
第一條保證了函式可以用於不能拷貝的型別
第二條降低了對要處理型別的要求,模板應該儘量減少對實參型別的要求

  • 例項化
模板的標頭檔案通常既包含宣告也包括定義
模板提供者保證模板被例項化時,模板的定義(包括成員的定義)都必須是可見的。
使用者需保證用來例項化模板的所有函式、型別以及型別關聯的運算子宣告都必須是可見的。

成員函式只有在被用到時才進行例項化。使得某些不完全複合模板操作要求的型別也能例項化類,但是不能用那些特性的操作。
在類模板自己的作用域中可以直接使用模板名而不提供實參,在外部必須提供
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{   BlobPtr ret=*this;//裡面不需要
}

需要先前置宣告為模板,將模板的一個特定例項宣告為友元時
一對一友好關係 
friend class BlobPtr<T>;
friend bool operator==<T> (const Blob<T>&, const Blob<T>&);

通用友好關係
template <typename X> friend class Pal2;//不需要前置宣告,使用不同的模板引數X
  • 類型別名
typedef只能醫用例項化的類
dypedef Blob<string> StrBlob;
新標準允許為類模板定義一個類型別名
template <typename T> using twin=pair<T, T>;
twin<string> authors;//authors是一個pair<string, string>
類型別名可以固定一個或多個模板引數
template <typename T> using partNo=pair<T, unsigned>;

一個特定檔案用到所有模板宣告通常一起放在檔案的開始位置。不必擔心編譯器由於未遇到你希望呼叫的函式而例項化一個並非你需要的版本。

C++預設通過作用域訪問的名字不是型別,所以如果我們希望使用模板型別引數的型別成員,必須顯式地告訴編譯器該名字是一個型別。
template <typename T>
typename T::value_type top(const T& c)  //返回型別是一個型別
  • 可以為函式和類模板提供預設實參。

無論何時使用類模板必須在模板名後加上尖括號,如果所有模板引數都提供了預設實參,而我們又希望使用預設實參,則加一個空的尖括號。

  • 成員模板不能是虛擬函式
在類外定義時,同時為類模板和成員模板提供模板引數列表
template <typename T>
template <typename It>
    Blob<T>::Blob(It b, It e);

多個檔案中例項化相同模板有額外開銷,在大系統中可能非常嚴重
可以顯式例項化,宣告必須在任何使用此例項版本的程式碼之前
extern template class Blob<string>;     //  例項化宣告
template  int compare(cons tint&, cons tint&);//例項化定義
顯式例項化的類模板,必須能用於模板的所有成員
  • 效率和靈活性
執行時繫結刪除器,刪除器是間接儲存的,呼叫del(p)時需要一次執行時的跳轉操作
del ? del(p) : delete p;// del(p)需要執行時跳轉到del的地址

編譯時繫結
del(p); //直接呼叫例項化的刪除器,無執行時額外開銷
  • 能應用於函式模板的型別轉換
1const轉換:非const物件的引用(或指標)傳遞給const的引用(指標)形參
2陣列或函式:如函式形參不是引用,可以對陣列或函式型別的實參應用正常的指標轉換
如函式引數型別不是模板引數,則可以進行正常型別轉換。
  • 函式模板顯式實參
1編譯器無法推斷(如返回型別) 2希望允許使用者控制模板例項化
template < typename T1, typename T2, typename T3>
T1 sum(T2, T3);

auto val3= sum<long long>(i, lng); //long long sum(int, lonog)
顯式模板實參由左至右的順序與對應的模板引數匹配。
由於尾至返回出現在引數列表之後,它可以使用函式的引數
template < typename It>
auto fcn(It beg, It end) -> decltype(*beg)

有時我們無法直接獲得所需要的型別

  • 標準型別轉換模板
    對Mod

template

  • 右值引用,move,forward
通常一個右值引用不能繫結到一個左值。
兩個例外
1當將一個左值傳遞給函式的右值引用,且此右值引用指向模板型別引數(如T&&)時,編譯器推斷模板型別引數為實參的左值引用型別。呼叫f3(i),T推斷為int&
通常不能定義一個引用的引用,但是通過類型別名或通過模板型別引數間接定義是可以的
2如果我們間隔建立一個引用的引用,則這些引用形成了摺疊
X& &、X& &&和X&& &都摺疊為型別X&
X&& &&摺疊為X&&

所以一個函式引數是指向模板引數型別的右值引用,則可以傳遞給他任意型別的實參。它對應實參的const屬性和左右值屬性得到保持。
通常用於模板轉發其實參或模板被過載。
過載通常按以下方式來
template <typename T> void f(T&&);      //繫結到非const右值
template <typename T> void f(const T&)  //繫結到左值和const右值

std::move的函式引數T&&是一個指向模板型別引數的右值引用,經過引用摺疊可以與任何型別的實參匹配,返回都是一個右值引用
static_cast指標右值引用的特例:顯式地將左值轉換為一個右值引用

當用於一個指向模板引數型別的右值引用函式引數時,forward會保持實參型別的所有細節。
template <typename T> void f(T &&arg)
{
    g(std::forward<T>(arg));
}
首先選擇更好的匹配,對於同樣好的匹配
優先選擇非模板函式
其次是更特例化的模板
如果找不出更好的,則此呼叫有歧義。
  • 可變引數模板
template <typename T, typename…Args>
void foo(const T &t, const Args& … rest);
sizeof…(Args)知道引數包中元素的個數,不會對實參求值

可變引數函式通常是遞迴的。為了終止遞迴需要定義一個非可變引數版本,否則會無限遞迴。

包擴充套件就是將它分解為構成的元素,對每個元素應用模式。通過在模式右邊放一個省略號來觸發擴充套件。
template < typename…Args>
ostream& errMsg(ostream &os, const Args& … rest)
{
    return print(os, debug_rep(rest)…);
}

轉發引數包
alloc.construct(first_free++, std::forward<Args>…);
  • 模板特例化
1通用模板的定義對特定型別不適合
2可以利用特定知識來編寫更搞笑的程式碼
template <>  //尖括號指出我們將為原模板所有模板引數提供實參
一個特例化版本本質上是一個例項,而不是函式名的一個過載版本
一個特殊的函式定義是特例化版本還是一個獨立的非模板函式,會影響到函式匹配。

為了特例化一個模板,原模板的宣告必須在作用域中。而且,在任何使用模板例項的程式碼之前,特例化版本的宣告也必須在作用域中。
通常模板及特例化版本應該宣告在同一個標頭檔案中。所有同名木板的宣告應該放在前面,然後是這些模板的特例化版本。

為了讓Sales_data的使用者能使用hash的特例化版本,我們應該在Sales_data的標頭檔案中定義該特例化版本

偏特化只能作用於類模板,而不能用於函式模板
偏特化模板引數列表是原模板的一個子集或者是一個特例化版本
template <typename T> struct remove_reference

template <typename T> struct remove_reference<T&>
template <typename T> struct remove_reference<T&&>

可以只特例化成員而不是類
template<>
void Foo<int>::Bar()