C++函数模板机制剖析

?思考:为什么函数模板可以和函数重载放在一起。C++编译器是如何提供函数模板机制的?

实例

#include<iostream>

using namespace std;

template<typename T>
void fun(T& a, T& b) {
	cout << "a" << a << "-b: " << b << endl;
	cout << "我是模板函数" << endl;
}

void fun(int a, char b) {
	cout << "a" << a << "-b: " << b << endl;
	cout << "我是函数调用" << endl;
}
int main() {

	int a = 1;
	char b = 'z';
	fun(a, b);//普通函数调用,可以进行隐式的类型转换
	fun(b, a);//  普通函数调用
	fun(a, a);//调用函数模板(本质:参数类型化),严格按照类型匹配,不会进行类型自动转换
	return 0;
}

编译

使用g++ test.c -S 生成test.s文件,内容如下:

	.file	"test.cpp"
.lcomm _ZStL8__ioinit,1,1
	.section .rdata,"dr"
.LC0:
	.ascii "a\0"
.LC1:
	.ascii "-b: \0"
.LC2:
	.ascii "\316\322\312\307\272\257\312\375\265\367\323\303\0"
	.text
	.globl	_Z3funic
	.def	_Z3funic;	.scl	2;	.type	32;	.endef
	.seh_proc	_Z3funic
_Z3funic:
.LFB1049:
	pushq	%rbp
	.seh_pushreg	%rbp
	pushq	%rbx
	.seh_pushreg	%rbx
	subq	$40, %rsp
	.seh_stackalloc	40
	leaq	128(%rsp), %rbp
	.seh_setframe	%rbp, 128
	.seh_endprologue
	movl	%ecx, -64(%rbp)
	movl	%edx, %eax
	movb	%al, -56(%rbp)
	movsbl	-56(%rbp), %ebx
	leaq	.LC0(%rip), %rdx
	movq	.refptr._ZSt4cout(%rip), %rcx
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	-64(%rbp), %edx
	movq	%rax, %rcx
	call	_ZNSolsEi
	leaq	.LC1(%rip), %rdx
	movq	%rax, %rcx
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	%ebx, %edx
	movq	%rax, %rcx
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
	movq	.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
	movq	%rax, %rcx
	call	_ZNSolsEPFRSoS_E
	leaq	.LC2(%rip), %rdx
	movq	.refptr._ZSt4cout(%rip), %rcx
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movq	.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
	movq	%rax, %rcx
	call	_ZNSolsEPFRSoS_E
	nop
	addq	$40, %rsp
	popq	%rbx
	popq	%rbp
	ret
	.seh_endproc
	.def	__main;	.scl	2;	.type	32;	.endef
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
.LFB1050:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$48, %rsp
	.seh_stackalloc	48
	.seh_endprologue
	call	__main
	movl	$1, -8(%rbp)
	movb	$122, -1(%rbp)
	movsbl	-1(%rbp), %edx
	movl	-8(%rbp), %eax
	movl	%eax, %ecx
	call	_Z3funic
	movl	-8(%rbp), %eax
	movsbl	%al, %edx
	movsbl	-1(%rbp), %eax
	movl	%eax, %ecx
	call	_Z3funic
	leaq	-8(%rbp), %rdx
	leaq	-8(%rbp), %rax
	movq	%rax, %rcx
	call	_Z3funIiEvRT_S1_
	movl	$0, %eax
	addq	$48, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.section .rdata,"dr"
.LC3:
	.ascii "\316\322\312\307\304\243\260\345\272\257\312\375\0"
	.section	.text$_Z3funIiEvRT_S1_,"x"
	.linkonce discard
	.globl	_Z3funIiEvRT_S1_
	.def	_Z3funIiEvRT_S1_;	.scl	2;	.type	32;	.endef
	.seh_proc	_Z3funIiEvRT_S1_
_Z3funIiEvRT_S1_:
.LFB1055:
	pushq	%rbp
	.seh_pushreg	%rbp
	pushq	%rsi
	.seh_pushreg	%rsi
	pushq	%rbx
	.seh_pushreg	%rbx
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	movq	%rcx, 32(%rbp)
	movq	%rdx, 40(%rbp)
	movq	40(%rbp), %rax
	movl	(%rax), %ebx
	movq	32(%rbp), %rax
	movl	(%rax), %esi
	leaq	.LC0(%rip), %rdx
	movq	.refptr._ZSt4cout(%rip), %rcx
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	%esi, %edx
	movq	%rax, %rcx
	call	_ZNSolsEi
	leaq	.LC1(%rip), %rdx
	movq	%rax, %rcx
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	%ebx, %edx
	movq	%rax, %rcx
	call	_ZNSolsEi
	movq	.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
	movq	%rax, %rcx
	call	_ZNSolsEPFRSoS_E
	leaq	.LC3(%rip), %rdx
	movq	.refptr._ZSt4cout(%rip), %rcx
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movq	.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
	movq	%rax, %rcx
	call	_ZNSolsEPFRSoS_E
	nop
	addq	$32, %rsp
	popq	%rbx
	popq	%rsi
	popq	%rbp
	ret
	.seh_endproc
	.text
	.def	__tcf_0;	.scl	3;	.type	32;	.endef
	.seh_proc	__tcf_0
__tcf_0:
.LFB1062:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	leaq	_ZStL8__ioinit(%rip), %rcx
	call	_ZNSt8ios_base4InitD1Ev
	nop
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.def	_Z41__static_initialization_and_destruction_0ii;	.scl	3;	.type	32;	.endef
	.seh_proc	_Z41__static_initialization_and_destruction_0ii
_Z41__static_initialization_and_destruction_0ii:
.LFB1061:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	movl	%ecx, 16(%rbp)
	movl	%edx, 24(%rbp)
	cmpl	$1, 16(%rbp)
	jne	.L6
	cmpl	$65535, 24(%rbp)
	jne	.L6
	leaq	_ZStL8__ioinit(%rip), %rcx
	call	_ZNSt8ios_base4InitC1Ev
	leaq	__tcf_0(%rip), %rcx
	call	atexit
	nop
.L6:
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.def	_GLOBAL__sub_I__Z3funic;	.scl	3;	.type	32;	.endef
	.seh_proc	_GLOBAL__sub_I__Z3funic
_GLOBAL__sub_I__Z3funic:
.LFB1063:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	movl	$65535, %edx
	movl	$1, %ecx
	call	_Z41__static_initialization_and_destruction_0ii
	nop
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.section	.ctors,"w"
	.align 8
	.quad	_GLOBAL__sub_I__Z3funic
	.ident	"GCC: (tdm64-1) 4.9.2"
	.def	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc;	.scl	2;	.type	32;	.endef
	.def	_ZNSolsEi;	.scl	2;	.type	32;	.endef
	.def	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c;	.scl	2;	.type	32;	.endef
	.def	_ZNSolsEPFRSoS_E;	.scl	2;	.type	32;	.endef
	.def	_ZNSt8ios_base4InitD1Ev;	.scl	2;	.type	32;	.endef
	.def	_ZNSt8ios_base4InitC1Ev;	.scl	2;	.type	32;	.endef
	.def	atexit;	.scl	2;	.type	32;	.endef
	.section	.rdata$.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, "dr"
	.globl	.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
	.linkonce	discard
.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_:
	.quad	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
	.section	.rdata$.refptr._ZSt4cout, "dr"
	.globl	.refptr._ZSt4cout
	.linkonce	discard
.refptr._ZSt4cout:
	.quad	_ZSt4cout

函数模板机制结论

  1. 编译器并不是把函数模板处理成能够处理任意类的函数
  2. 编译器从函数模板通过具体类型产生不同的函数
  3. 编译器会对函数模板进行两次编译
    在声明的地方对模板代码本身进行编译

在调用的地方对参数替换后的代码进行编译
其中的两次编译发生了什么?

  • 第一个编译根据函数模板进行词法分析、语法分析、句法分析产生一个简要的函数模板类型
  • d第二次编译根据调用产生一个具体的函数原型,若还有新的调用,则再产生一个新的函数原型,并将函数原型放在test.s中
### C++ 函数模板显式实例化的概念与实现 #### 显式实例化的作用 在 C++ 中,函数模板的显式实例化是一种机制,用于手动告诉编译器为特定类型生成模板的具体版本。这种方式可以减少重复编译的时间开销,并优化链接阶段的行为[^2]。 #### 显式实例化的语法 显式实例化的声明形式如下: ```cpp extern template 返回类型 函数名<类型>(参数列表); ``` 而其定义则通过省略 `extern` 来完成实际的实例化操作: ```cpp template 返回类型 函数名<类型>(参数列表); ``` #### 示例代码 以下是一个完整的例子,展示如何对一个简单的函数模板进行显式实例化: ```cpp // 定义函数模板 template <typename T> void display(T value) { std::cout << "Value is: " << value << std::endl; } // 声明显式实例化 extern template void display<int>(int); // 使用函数模板 #include <iostream> int main() { display(10); // 调用 int 版本的显式实例化 display<double>(3.14); // 隐式实例化 double 版本 return 0; } ``` 在这个例子中,`display<int>` 是被显式实例化的版本,因此编译器不会自动为其生成代码,而是依赖于其他地方的实际定义[^2]。 #### 实现细节说明 当使用 `extern template` 声明时,表示该模板实例将在另一个翻译单元中提供。这有助于避免多次实例化同一模板而导致的目标文件膨胀问题[^5]。 对于显式实例化的定义部分,通常放在单独的源文件中以供链接器使用。例如,在另一个 `.cpp` 文件中可能有如下内容: ```cpp // 提供显式实例化的定义 template void display<int>(int); ``` 这样做的好处在于能够集中管理某些常用类型的模板实例,从而提高构建效率并降低最终可执行程序的大小[^1]。 #### 使用场景分析 显式实例化适用于以下几种情况: - **性能优化**:当项目规模较大且存在大量相同类型的模板实例时,可以通过显式实例化减少冗余计算。 - **跨模块共享**:如果多个模块都需要访问同一个模板实例,则可以在单一位置创建其实例并通过链接方式分享给所有使用者[^4]。 - **调试方便**:由于显式实例化的代码只存在于一处,所以更容易定位错误或修改逻辑而不影响全局行为。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值