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;
}

就一个函数的事,多么简洁而优雅!模板就如蓝图一样,对于实例化模板参数的每一种类型,都从模板中产生一个不同的实体,下面就是测试

#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是调用参数

模板参数要求必须要全部推导出来,函数模板可以通过传入实参来推导模板参数,如果推导失败,那么就会发生错误

max(1, 1.5); //max(int, double), 推导失败

如果没有全部推导,也会发生错误

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)就会匹配一般函数

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容