C++ 模版

模板(template)是為了支持泛型編程(Generic programming)而存在的,所謂泛型,也就是不依賴于具體類型,wiki對其定義如下

Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters.

Generic programming --wikipedia

為了更直觀的了解,我們先看看相對于一般的編程方式,范型編程是怎么樣的

#include <iostream>

int max_normal(int a, int b) {
    return a > b? a : b;
}

template <typename T>
T max_generic(T a, T b) {
    return a > b? a : b;
}
#define Log(x) std::cout<<x<<std::endl;
int main() {
    //int
    Log(max_normal(1, 2)); //2
    Log(max_generic(1, 2));//2
    
    //float
    Log(max_normal(1.1, 1.2));//1
    Log(max_generic(1.1, 1.2));//1.2
    return 0;
}

對于多種數據類型,普通函數要聲明不同的版本

float max(float, float);
int max(int, int);

這將導致大量的重復工作,為此,我們需要解放自己,這時候泛型編程登場了

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

就一個函數的事,多么簡潔而優(yōu)雅!模板就如藍圖一樣,對于實例化模板參數的每一種類型,都從模板中產生一個不同的實體,下面就是測試

#include <iostream>
template <typename T>
T max_generic(T a, T b) {
    return a > b? a : b;
}
#define Log(x) std::cout<<x<<std::endl;
int main() {
    //使用 int 進行實例化
    Log(max_generic(1, 2));
    //使用 double 進行實例化
    Log(max_generic(1.1, 1.2));
    return 0;
}

然后我們就會得到兩個實例...

//自動生成的
int max_generic(int a, int b) {
    return a > b? a : b;
}

double max_generic(double a, double b) {
    return a > b? a : b;
}

如果還不相信的話,我們就來看看對應的匯編代碼

g++ -g -c source.cpp & objdump -S source.o > source_obj.s

1.o:    file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_main:
; int main() {
       0:   55  pushq   %rbp
       1:   48 89 e5    movq    %rsp, %rbp
       4:   48 83 ec 10     subq    $16, %rsp
       8:   bf 01 00 00 00  movl    $1, %edi
       d:   be 02 00 00 00  movl    $2, %esi
; max(1, 2);
      12:   e8 00 00 00 00  callq   0 <_main+0x17>
      17:   f2 0f 10 05 91 00 00 00     movsd   145(%rip), %xmm0
      1f:   f2 0f 10 0d 91 00 00 00     movsd   145(%rip), %xmm1
; max(1.1, 1.2);
      27:   89 45 fc    movl    %eax, -4(%rbp)
      2a:   e8 00 00 00 00  callq   0 <_main+0x2F>
      2f:   31 c0   xorl    %eax, %eax
; }
      31:   f2 0f 11 45 f0  movsd   %xmm0, -16(%rbp)
      36:   48 83 c4 10     addq    $16, %rsp
      3a:   5d  popq    %rbp
      3b:   c3  retq
      3c:   0f 1f 40 00     nopl    (%rax)

__Z3maxIiET_S0_S0_:
; T max(T x, T y) {
      40:   55  pushq   %rbp
      41:   48 89 e5    movq    %rsp, %rbp
      44:   89 7d fc    movl    %edi, -4(%rbp)
      47:   89 75 f8    movl    %esi, -8(%rbp)
; return x > y? x : y;
      4a:   8b 75 fc    movl    -4(%rbp), %esi
      4d:   3b 75 f8    cmpl    -8(%rbp), %esi
      50:   0f 8e 0b 00 00 00   jle 11 <__Z3maxIiET_S0_S0_+0x21>
      56:   8b 45 fc    movl    -4(%rbp), %eax
      59:   89 45 f4    movl    %eax, -12(%rbp)
      5c:   e9 06 00 00 00  jmp 6 <__Z3maxIiET_S0_S0_+0x27>
      61:   8b 45 f8    movl    -8(%rbp), %eax
      64:   89 45 f4    movl    %eax, -12(%rbp)
      67:   8b 45 f4    movl    -12(%rbp), %eax
      6a:   5d  popq    %rbp
      6b:   c3  retq
      6c:   0f 1f 40 00     nopl    (%rax)

__Z3maxIdET_S0_S0_:
; T max(T x, T y) {
      70:   55  pushq   %rbp
      71:   48 89 e5    movq    %rsp, %rbp
      74:   f2 0f 11 45 f8  movsd   %xmm0, -8(%rbp)
      79:   f2 0f 11 4d f0  movsd   %xmm1, -16(%rbp)
; return x > y? x : y;
      7e:   f2 0f 10 45 f8  movsd   -8(%rbp), %xmm0
      83:   66 0f 2e 45 f0  ucomisd -16(%rbp), %xmm0
      88:   0f 86 0f 00 00 00   jbe 15 <__Z3maxIdET_S0_S0_+0x2D>
      8e:   f2 0f 10 45 f8  movsd   -8(%rbp), %xmm0
      93:   f2 0f 11 45 e8  movsd   %xmm0, -24(%rbp)
      98:   e9 0a 00 00 00  jmp 10 <__Z3maxIdET_S0_S0_+0x37>
      9d:   f2 0f 10 45 f0  movsd   -16(%rbp), %xmm0
      a2:   f2 0f 11 45 e8  movsd   %xmm0, -24(%rbp)
      a7:   f2 0f 10 45 e8  movsd   -24(%rbp), %xmm0
      ac:   5d  popq    %rbp
      ad:   c3  retq

我們可以看到max一共有兩個函數名

  • __Z3maxIiET_S0_S0 = int max(int, int)
  • __Z3maxIdET_S0_S0,=double max(double, double)

好嘞,對底層的驗證點到為止,那么接下來我們將迎來重頭戲

模板參數的推導(deduction)

函數模版有兩種類型的參數

  • template<typename T> : T是模板參數
  • max(T x, T y) : x, y是調用參數

模板參數要求必須要全部推導出來,函數模板可以通過傳入實參來推導模板參數,如果推導失敗,那么就會發(fā)生錯誤

max(1, 1.5); //max(int, double), 推導失敗

如果沒有全部推導,也會發(fā)生錯誤

template<typename T1, typename T2>
void foo(T1 x);

foo(1); //只有T1被推導出來,T2沒有被推導,錯誤!
foo<int, int>(1); //顯示指定模板參數,成功

一般而言,不會去轉化實參類型以匹配已有的實例,不過有些情況除外

  • const轉換:

    • 如果模板形參為const引用,則其可以接受const或非const引用
    • 如果模板形參為const指針,則其可以接受const或非const指針
    • 如果模板形參不是引用或指針(值傳遞),則形參和實參都忽略const
  • 數組或函數到指針的轉換

    • 如果模板形參不是引用或指針(值傳遞),則數組會轉化為指針,數組實參將當作指向其第一個元素的指針;
    • 如果模板形參不是引用或指針(值傳遞),則函數會轉化為指針,函數實參將當作指向函數類型的指針;
template<typename T>
void fobj(T x) {
    x = 3;
}

template<typename T>
void fref(const T& v) {
}

int main() {
    int a = 1;
    const int b = 2;

    fobj(b);

    fref(a);
    fref(b);

    return 0;
}

但是有時候,模板推斷可能會出乎我們的預料

template<typename T>
void fref(T& x) {
}

int main() {
    const int b = 4;
    fref(b);//ok!
    return 0;
}

道理上來說,這樣的推斷是錯誤的,但是我們可以看到在函數中并沒有對x進行任何修改,因此可以匹配

但是如果在函數中加入x=3那么,編譯就會報錯,這就在情理之中了

另外從C++11開始,已經可以用模板來推導模板參數了

例如

#include <iostream>

template<typename T, int F>
void fun(T i) {
    i = i + F;
}

template<typename T>
void test(T i) {
    std::cout << std::is_same<T, void(*)(int)>::value << std::endl; // true, T被推導為函數指針void(*)(int)
    i(3);
}

int main() {
    test(fun<int, 2>);
    return 0;
}

函數模板的重載與匹配

#include <iostream>
#include <typeinfo>

#define Log(x) std::cout<<x<<std::endl

const int & max(const int &a, const int &b) {
    Log("normal");
    return a > b? a : b;
}
template<typename T>
T const & max(const T &a, const T &b) {
    Log("template");
    return a > b? a : b;
}
template<typename T>
T const & max(const T &a, const T &b, const T &c) {
    Log("template");
    return max(max(a, b), c);
}
int main() {
    Log(max(1, 2));
    Log(max(1.2, 2.4));
    Log(max(1, 2, 3));
    return 0;
}

注意,實例化模版也是有代價的,如果能通同時匹配到一般函數和模板函數,就使用一般函數

模板不允許隱式轉換,因此如果兩個類型不同,就會考慮一般函數(如上),max(int, float)就會匹配一般函數

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • C++ 模版 模板是C++支持參數化多態(tài)的工具,使用模板可以使用戶為類或者函數聲明一種一般模式,使得類中的某些數據...
    jorion閱讀 1,966評論 0 9
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,655評論 1 51
  • 那些生活的疲憊,日子的艱辛,通過臉這個窗口,一覽無余的暴露出來,毫無征兆,無聲無息,卻讓人一目了然,盡收眼底。 臉...
    虛懷若谷8閱讀 469評論 0 0
  • As I began to love myself I found that anguish and emotio...
    攜晨閱讀 237評論 0 0
  • 你常說這人世荒涼 愛也浮光掠影 恨也波瀾不驚 熾烈的火焰盛滿了凄清蕭寂 而我不發(fā)一言 背對著你 躲在這火焰的陰影中
    千秋_閱讀 492評論 0 5

友情鏈接更多精彩內容