模板的魔力发生在编译时

1. 模板的魔力发生在编译时。

如下示例中,编译器看到absval的模板定义,然后看到对absval函数模板的调用。编译器检查函数参数的类型,并根据函数参数的类型确定模板参数。编译器将该模板参数替换为T,并生成一个新的absval函数实例,定制为模板参数类型。
main.cpp

// g++    main.cpp   -std=c++14   --save-temps
template<class T>
T absval(T val){
    if(val >= 0){
        return val;
    }
    else {
        return -val;
    }
}

int main() {
    int a = absval(4);
    float b = absval(-21.f);
    double c = absval(100.0);    
}

编译生成的汇编代码如下,有以下代码可以看出,每用一个类型调用模板函数在汇编代码里就会有对应的代码生成,因此会有二进制文件膨胀的风险。

	.arch armv8-a
	.file	"main.cpp"
	.text
	.align	2
	.global	main
	.type	main, %function
main:
.LFB1:
	.cfi_startproc
	stp	x29, x30, [sp, -32]!
	.cfi_def_cfa_offset 32
	.cfi_offset 29, -32
	.cfi_offset 30, -24
	mov	x29, sp
	mov	w0, 4
	bl	_Z6absvalIiET_S0_
	str	w0, [sp, 16]
	fmov	s0, -2.1e+1
	bl	_Z6absvalIfET_S0_
	str	s0, [sp, 20]
	mov	x0, 4636737291354636288
	fmov	d0, x0
	bl	_Z6absvalIdET_S0_
	str	d0, [sp, 24]
	mov	w0, 0
	ldp	x29, x30, [sp], 32
	.cfi_restore 30
	.cfi_restore 29
	.cfi_def_cfa_offset 0
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.section	.text._Z6absvalIiET_S0_,"axG",@progbits,_Z6absvalIiET_S0_,comdat
	.align	2
	.weak	_Z6absvalIiET_S0_
	.type	_Z6absvalIiET_S0_, %function
_Z6absvalIiET_S0_:
.LFB2:
	.cfi_startproc
	sub	sp, sp, #16
	.cfi_def_cfa_offset 16
	str	w0, [sp, 12]
	ldr	w0, [sp, 12]
	cmp	w0, 0
	blt	.L4
	ldr	w0, [sp, 12]
	b	.L5
.L4:
	ldr	w0, [sp, 12]
	neg	w0, w0
.L5:
	add	sp, sp, 16
	.cfi_def_cfa_offset 0
	ret
	.cfi_endproc
.LFE2:
	.size	_Z6absvalIiET_S0_, .-_Z6absvalIiET_S0_
	.section	.text._Z6absvalIfET_S0_,"axG",@progbits,_Z6absvalIfET_S0_,comdat
	.align	2
	.weak	_Z6absvalIfET_S0_
	.type	_Z6absvalIfET_S0_, %function
_Z6absvalIfET_S0_:
.LFB3:
	.cfi_startproc
	sub	sp, sp, #16
	.cfi_def_cfa_offset 16
	str	s0, [sp, 12]
	ldr	s0, [sp, 12]
	fcmpe	s0, #0.0
	bge	.L10
	b	.L11
.L10:
	ldr	s0, [sp, 12]
	b	.L9
.L11:
	ldr	s0, [sp, 12]
	fneg	s0, s0
.L9:
	add	sp, sp, 16
	.cfi_def_cfa_offset 0
	ret
	.cfi_endproc
.LFE3:
	.size	_Z6absvalIfET_S0_, .-_Z6absvalIfET_S0_
	.section	.text._Z6absvalIdET_S0_,"axG",@progbits,_Z6absvalIdET_S0_,comdat
	.align	2
	.weak	_Z6absvalIdET_S0_
	.type	_Z6absvalIdET_S0_, %function
_Z6absvalIdET_S0_:
.LFB4:
	.cfi_startproc
	sub	sp, sp, #16
	.cfi_def_cfa_offset 16
	str	d0, [sp, 8]
	ldr	d0, [sp, 8]
	fcmpe	d0, #0.0
	bge	.L16
	b	.L17
.L16:
	ldr	d0, [sp, 8]
	b	.L15
.L17:
	ldr	d0, [sp, 8]
	fneg	d0, d0
.L15:
	add	sp, sp, 16
	.cfi_def_cfa_offset 0
	ret
	.cfi_endproc
.LFE4:
	.size	_Z6absvalIdET_S0_, .-_Z6absvalIdET_S0_
	.ident	"GCC: (Ubuntu 13.1.0-8ubuntu1~22.04) 13.1.0"
	.section	.note.GNU-stack,"",@progbits

编写函数模板比编写普通函数更难。当你编写一个像absval这样的模板时,问题在于你不知道实际上T将是什么类型或类型。因此,函数必须是通用的。编译器将阻止你使用某些类型作为T。它的限制是由模板体对T的使用方式隐含确定的。

具体来说,absvalT施加了以下限制:

  1. T必须是可复制的。这意味着你必须能够复制类型为T的对象,这样就可以将参数传递给函数并返回结果。如果T是一个类类型,那么该类必须有一个可访问的拷贝构造函数,也就是说,拷贝构造函数不能是私有的。
  2. T必须可以使用<运算符与0进行比较。你可以重载<运算符,或者编译器可以将0转换为T,或者将T转换为int
  3. 对于类型为T 的操作数,必须定义一元操作符-。结果类型必须是T或编译器可以自动转换为T的类型。

内置的数字类型都满足这些要求。有理数类型也满足这些要求,因为它支持自定义的运算符。

举个例子,字符串类型不满足这些要求,因为它缺少与整数作为右操作数进行比较的比较运算符,并且缺少一元否定(-)运算符。假设你尝试在字符串上调用absval编译会报错。
编译器抱怨在std::string上缺少比较和否定运算符。在使用模板时提供有用的错误消息的一个困难在于是给出在模板使用的行号还是模板定义的行号。有时,你会同时得到两者。有时,除非你尝试使用模板,否则编译器无法报告模板定义中的错误。其他错误则可以立即报告。

2. 编译器会优先选择非模板函数而不是模板函数

编译器会优先选择非模板函数而不是模板函数,但如果在非模板函数的参数类型和参数类型之间找不到很好的匹配,则会使用模板函数。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值