模板函数冲突
通过前面章节的学习,我们已经知道了什么是函数模板,什么是模板函数?函数模板是模板函数的抽象,模板函数是函数模板的具现。今天,我们主要讲解一下什么是模板函数冲突,模板函数冲突的原因及相应的解决方法。
(一)模板函数冲突
为了说清什么是模板函数冲突,我们先从一个例子开始。
例1 模板函数冲突
func1.cpp的代码:
#include<iostream> template<typename T> void func(T const &v) { std::cout<<"func1.cpp-func:"<<v<<std::endl; } void caller1() { func(1); func(0.1); }func2.cpp的代码:
#include<iostream> template<typename T> void func(T const & v) { std::cout<<"func2.cpp-func:"<<v<<std::endl; } void caller2() { func(2); func(0.2f); }main.cpp的代码:
void caller1(); void caller2(); int main() { caller1(); caller2(); return 0; }运行效果如下:
图1 模板函数冲突的效果图
在例1中,整个程序由三部分组成,一个是func1.cpp,其中定义了func函数模板和caller1;一个是func2.cpp,其中定义了func函数模板和caller2;最后一个是main.cpp
,其中定义了主函数main,并在主函数中分别调用了caller1和caller2。
预期目标:输出结果应该为图2所示:
图2 预期输出效果图
但是实际输出效果却为图1所示,这是因为caller1中生成的模板函数和caller2中生成的模板函数冲突了,才会导致输出结果不能按照预期输出。
通过例1,我们明白了模板函数冲突就是指由两个函数模板原型一样,但是,函数体不一样的函数模板,生成,函数原型一样,但是函数体不一样的两个模板函数所造成的冲突。
(二)模板函数冲突的原因
上面小节,我们讲了什么是模板函数冲突,现在,我们来共同分析一下模板函数冲突的原因。我们仍然以例1为例,我们主要分析一下为什么fun2.cpp-func:2会变为func1.cpp-func:2。
为了讲清楚模板函数冲突的原因,我们分步骤进行:
(1)观察func1.o的符号表
1、输入编译命令:g++ -c func1.cpp
2、输入查看符号表命令:nm -C func1.o | grep func
最终效果如图3所示:
图3 func1.o的模板函数
(2)观察func2.o的符号表
1、输入编译命令:g++ -c func2.cpp
2、输入查看符号表命令:nm -C func2.o | grep func
最终效果如图4所示:
图4 func2.o的模板函数
(3)对比func1.o和func2.o的符号表
1、分析func1.cpp
由于在func1.cpp中定义了函数caller1,而在函数caller1中又使用了函数模板func,所以编译器为func1.o生成了两个模板函数,分别针对double和int型,因为小数默认为double类型。
2、分析func2.cpp
由于在func2.cpp中定义了函数caller2,而在函数caller2中又使用了函数模板func,所以编译器为func2.o生成了两个模板函数,分别针对float和int型,因为这里为小数加了后缀f,所以编译器将其识别为float类型。
(4) 结论
1、输入编译链接命令:g++ -o test func1.o func2.o main.cpp
2、查看test的符号表:nm -C test.exe | grep func
最终效果如图5所示:
图5 test.exe的模板函数
通过观察图5可知,编译器为test.exe生成了4个模板函数,1个针对double;1个针对float;2个针对int。问题就出现在int类型上,因为当我们链接程序的时候,是先让链接器扫描func1.o,所以导致链接器先识别了func1.o中的int型函数模板实例,而当链接器扫描到func2.o的时候,虽然也遇见了一个int型的函数模板实例,但是因为这两个模板函数的实例原型一致,导致链接器只使用第一个int模板函数,而不使用第二个int模板函数,所以,整个程序的输出结果不能按照预期输出。
倘若,我们更改一下链接命令,让其先扫描func2.o,那么效果如图6所示:
图6 模板函数冲突的另一种效果图
(三)解决模板函数冲突的方法
归根揭底,例1中模板函数冲突的原因就是因为两个模板函数的符号名完全相同;所以,若想解决冲突,只要让它们的符号名不同即可,经常使用的方法就是命名空间。
下面,我们一起来看一下,如果使用命名空间,例1的写法如何。
例 2 使用命名空间解决模板函数冲突
func1.cpp代码:
#include<iostream> namespace func1 { template<typename T> void func(T const &v) { std::cout<<"func1.cpp-func:"<<v<<std::endl; } void caller1() { func(1); func(0.1); } }func2.cpp代码:
#include<iostream> namespace func2 { template<typename T> void func(T const & v) { std::cout<<"func2.cpp-func:"<<v<<std::endl; } void caller2() { func(2); func(0.2f); } }main.cpp代码:
namespace func1 { void caller1(); } namespace func2 { void caller2(); } int main() { func1::caller1(); func2::caller2(); return 0; }编译代码,效果如图7所示:
图7 模板函数冲突解决的效果图
在例2中,因为使用了命名空间技术,使得func1.cpp中的模板函数符号名和func2.cpp中的模板函数符号名不相同,所以解决了模板函数冲突导致的问题,输出结果符合预期效果。
为了让大家相信func1.cpp的模板函数的符号名和func2.cpp中的模板函数的符号名已经不同,这里给出了它们的符号表,如下:
图8 func1.o的符号表
图9 func2.o的符号表
通过仔细观察图8和图9可知,func1.cpp中的int模板函数符号名为func1::func<int>(int const &),而func2.cpp中的int模板函数符号名为func2::func<int>(int const&),
所以当编译链接他们的时候,就不会混淆,图10是test.exe的效果图,如下:
图10 test.exe 的符号表
(四)小结
今天,主要讲了什么是模板函数冲突,以及冲突的原因,并给出了详细的符号表,最后讲解了解决模板函数冲突的方法就是在代码中使用命名空间,这样编译器就可以解决模板函数冲突的问题。