C++泛型一:模板

sgqmax發表於2024-10-31

資料型別給程式設計帶來的困擾及解決方案

int maxt(int, int);
double maxt(double, double);

若有一種佔位符T,能夠代替型別,便可以簡化程式碼的冗餘編寫

T maxt(T,T);

C++模板

模板宣告如下

template<typename T1, ...>

template是C++的模板宣告關鍵字,尖括號內為模板引數列表
typename為型別佔位符宣告關鍵字

template<typename T>
T maxt(T x, T y){
    return (x>y)? x: y;
}

函式模板

預編譯階段,當程式中呼叫函式模板時,編譯器會用實際型別替換型別佔位符生成實體函式
若編譯器可以從函式實參中推匯出模板引數所需型別,則可以不傳入模板引數

template<typename T>
T maxt(T x, T y){
    return (x>y)?x:y;
}

int main(int argc, char* argv[]){
    // std::cout<< maxt<int>(4,6)<< std::endl;
    std::cout<< maxt(4,6)<< std::endl;

    return 0;
}

類别範本

在宣告類時,使用template進行模板宣告即可

template<typename T>
class Circle{
public:
    Circle(T r);
}

若在類别範本外實現成員函式,則必須宣告為函式模板

template<typename T>
Circle<T>::Circle(T r){}

在呼叫時,需要在類名後使用尖括號傳遞具體型別

Circle<int> circle;

STL的模板程式設計對物件導向技術並不感興趣,其認為類對資料的過度封裝影響程式的執行效率
而為了更好的管理程式碼,所以STL中使用大量沒有訪問許可權的struct製作的類别範本

變數模板

變數模板,將模板擴充套件到變數
pi<T>的實現
Tdouble時,返回3.14
Tint時,返回3
Tstring時,返回"3.14""pi"

C++新標準對泛型設計的努力

auto和decltype

C++11中,auto關鍵字,用來推導變數的資料型別auto a=100;
auto型別的獲取可透過編譯器的型別記憶能力或decltype的型別提示來推導

利用型別記憶推導複雜型別

auto目前能力有限,只對系統的內建資料型別有效
對於使用者自定義型別或複雜型別,只有當編譯器取得足夠經驗後,才具備推導能力

map<int,map<int,int>>::const_iterator iter1=map1.begin();
auto iter2=map1.begin();

由於前一條語句告知了編譯器map1.begin()的型別,在處理第二條語句時,便利用了記憶能力自動推匯出iter2的型別

decltype表示式對推導函式返回值型別進行指導

變數型別難以確定的問題一般出現在函式返回值上,C++11可以使用decltype對函式返回值的型別推導工作進行指導
當返回auto型別,需要編譯器對函式返回值型別進行推導時,可用decltype對該推導工作進行指導

template<typename T, typename U>
auto Multiply(T t, U u)->decltype(t*u){
    return t*u;
}

這種使用auto作為函式返回值型別的稱為auto返回值佔位

auto看作資料型別,則auto也是一種泛型,只不過無須關鍵字typename宣告
且實際型別不是由實參顯式提供,而是根據型別操作相關歷史記憶及應用程式提供的推導思路

模板引數

根據引數實參的性質,模板引數分為型別引數,非型別引數和模板定義型引數三種

型別引數

用關鍵字typename宣告的引數
型別引數的型別實參包括:

  • 系統內建的型別
  • 使用者自定義的資料型別
  • 編譯器剛學到的類别範本實體
  • typename定義的型別別名

非型別引數

C++允許在模板引數列表中定義普通變數或物件,如template<typename T, int a>
由於模板引數是在預編譯階段進行傳遞並被編譯的,故這種非型別引數在模板程式碼內是常量,不能修改
對於這種引數,目前C++僅支援整型int(或可轉為int的型別,如bool),列舉,指標和引用型別

C++11支援非型別引數在定義時賦值,如template<typename T, int b=100>

模板定義型引數

以類别範本作為類别範本引數,除了強調這個型別引數必須為類别範本外,還強調該類别範本的引數個數

// 單模板引數的類别範本
template<typename T>
struct S_Tmp{};

// 多模板引數的類别範本
template<typename T, typename R>
struct D_Tmp{};

// 以單引數類别範本作為引數的類别範本
template<template<typename S>class T>
struct MyTest{};

int main(){
  MyTest<S_Tmp> tt1;
//   MyTest<D_Tmp> tt1; // error
  return 0;
}

模板形參和實參的結合

函式模板實參的隱式結合

編譯器可以根據函式實參型別推匯出模板形參所對應的實參,這種在呼叫函式模板時可以省略模板引數列表
由於函式呼叫語句中不提供函式返回值的型別資訊,所以模板的返回值型別佔位符必須與某個形參的佔位符相同

指標實參

C++中,指標是一種資料型別,因此可作為模板實參

修飾字const和&的使用

可以在模板呼叫引數列表中使用修飾字const&

template<typename T1, typename T2>
const T1& add(const T1& a, const T2& b){
    return a;
}

模板特例化與模板具現

模板特例化

資料型別的變化通常與業務邏輯無關
若有個別資料型別所對應的演算法與其他型別對應的演算法不同,這類演算法就要單獨編寫

函式模板的特化

如判斷大小的函式,數值型別與字串型別的比較演算法是不一樣的,應該分開實現

template<typename T>
T mymax(T a, T b){
    return a>b?a:b;
}
template<>
char* mymax(char* a, char* b){
    return (strcmp(a,b)<0)?b:a;
}

使用template<>是為了將其納入maxt模板體系

類别範本的特化與偏特化

// 普通模板
template<typename T1, typename T2>
struct Test{};

// 偏特化模板
template<typename T>
struct Test<int, T>{};

// 全特化模板
template<>
struct Test<int, float>{};

模板的具現

編譯器在匹配模板生成實體程式碼時的優先順序

  1. 特化模板(函式或類)
  2. 偏特化模板(類)
  3. 普通模板(函式或類)

相關文章