用汇编的眼光看c++(之模板函数)17(转)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
如果说模板类定义的是一种数据类型,那么模板函数定义的就是一种函数。既然是函数,那么就有输入数据和输出数据。和模板类的概念差不多,模板函数的初衷也是为了在函数操作上抽取共同的特性,屏蔽的是类型的不同和差异。我们可以通过下面一个简单的代码说明问题:
- int int_compare(int a, int b)
- {
- return a > b ? a : b;
- }
- double double_compare(double a, double b)
- {
- return a > b ? a : b;
- }
上面的一段代码是取较大值的一段代码。两个函数之间最大的差别就是输入数据类型和输出数据类型之间的差别,那我们有没有一种办法可以屏蔽这种数据类型之间的差别呢?有。那就是函数模板:
- template <typename type>
- type compare(type a, type b)
- {
- return a > b ? a : b;
- }
可以看到,模板函数和普通函数没有什么区别,只是在函数的上面把类型抽象成了type,那么模板函数应该怎么使用呢?
- 246: int i_value = compare(2, 3);
- 00401458 push 3
- 0040145A push 2
- 0040145C call @ILT+10(compare) (0040100f)
- 00401461 add esp,8
- 00401464 mov dword ptr [ebp-4],eax
- 247: double d_value = compare(2.3, 3.1);
- 00401467 push 4008CCCCh
- 0040146C push 0CCCCCCCDh
- 00401471 push 40026666h
- 00401476 push 66666666h
- 0040147B call @ILT+5(compare) (0040100a)
- 00401480 add esp,10h
- 00401483 fstp qword ptr [ebp-0Ch]
- 248: }
汇编代码表明,两个compare调用的函数地址并不是一致的。其中整数的compare地址是0x40100f,而double的地址是 0x0040100a。这说明编译器在编译的时候帮我们同时生成了两个compare函数。所以说,模板类的本质就是在编译器增加判断处理工作的同时,减 少手工的重复劳动。同时和模板类不一样,模板函数不需要显示定义函数的参数类型,这是因为可以从入参判断出函数的类型。
如果参数类型是 class类型呢? 我们可以试一试。首先定义基本class:
- class data
- {
- int value;
- public:
- explicit data(int m): value(m) {}
- ~data() {}
- int get_value() { return value;}
- int operator > (data& d) {return this->get_value() > d.get_value();}
- };
接着,我们调用compare函数:
- 256: data m(4), n(2);
- 0040148D push 4
- 0040148F lea ecx,[ebp-10h]
- 00401492 call @ILT+40(data::data) (0040102d)
- 00401497 mov dword ptr [ebp-4],0
- 0040149E push 2
- 004014A0 lea ecx,[ebp-14h]
- 004014A3 call @ILT+40(data::data) (0040102d)
- 004014A8 mov byte ptr [ebp-4],1
- 257: data p = compare(m,n);
- 004014AC mov eax,dword ptr [ebp-14h]
- 004014AF push eax
- 004014B0 mov ecx,dword ptr [ebp-10h]
- 004014B3 push ecx
- 004014B4 lea edx,[ebp-18h]
- 004014B7 push edx
- 004014B8 call @ILT+15(compare) (00401014)
- 004014BD add esp,0Ch
- 258: }
256行: data构造了两个基本变量m和n
257行: 我们调用模板函数compare, 函数地址为0x401014,注意dx为p的地址,也就是堆栈临时变量的地址
为了看到算术符>重载,我们跟进compare函数:
- 241: return a > b ? a : b;
- 0040212B lea eax,[ebp+10h]
- 0040212E push eax
- 0040212F lea ecx,[ebp+0Ch]
- 00402132 call @ILT+55(data::operator>) (0040103c)
- 00402137 test eax,eax
- 00402139 je compare+53h (00402143)
- 0040213B lea ecx,[ebp+0Ch]
- 0040213E mov dword ptr [ebp-18h],ecx
- 00402141 jmp compare+59h (00402149)
- 00402143 lea edx,[ebp+10h]
- 00402146 mov dword ptr [ebp-18h],edx
- 00402149 mov eax,dword ptr [ebp-18h]
- 0040214C mov dword ptr [ebp-10h],eax
- 0040214F mov ecx,dword ptr [ebp-10h]
- 00402152 mov edx,dword ptr [ecx]
- 00402154 mov eax,dword ptr [ebp+8]
- 00402157 mov dword ptr [eax],edx
- 00402159 mov ecx,dword ptr [ebp-14h]
- 0040215C or ecx,1
- 0040215F mov dword ptr [ebp-14h],ecx
- 00402162 mov byte ptr [ebp-4],1
- 00402166 lea ecx,[ebp+0Ch]
- 00402169 call @ILT+25(data::~data) (0040101e)
- 0040216E mov byte ptr [ebp-4],0
- 00402172 lea ecx,[ebp+10h]
- 00402175 call @ILT+25(data::~data) (0040101e)
- 0040217A mov eax,dword ptr [ebp+8]
我们发现compare模板语句下面构建了很多汇编语句,有一些冗长,我们可以大略介绍一下:
(1) 开头调用call 0x0040103C函数就是调用重载运算符函数,[ebp-18h]表示即将被复制的是a数据还是b数据
(2) 比较返回结果后,开始复制数据,具体见0x402157,其中临时变量[ebp-14h]和临时变量[ebp-4]的操作可以忽略
(3) 函数返回前,对临时变量a和b进行析构处理,见代码0x402169和代码0x402175。
注意:
(1)编写模板函数前先保证自己的函数是编写正确的
(2)模板函数的优先级低于非模板函数
(3)模板函数的类型可以是自定义类型,也可以是c、c++语言的基本类型
(4)模板函数的使用经常和类的算术运算符混合使用,注意技巧
(5)模板函数中涉及指针部分的内容,务必注意
【预告: 下一篇模板主要讲述特化模板、缺省模板】