在按照书本打代码的时候发现怎么编译都通过不了,都出现了一种未定义的引用的例子,本来觉得可能是自己的CMakeLIsts.txt写错了,之后查找了半天,发现还是因为使用类模板出了错。因为以前也没有使用过类模板,就直接按照普通的类使用.h和.cpp文件将他们分开写了,最后没想到在使用的时候就出现了错误,所以这里就对模板的使用做个记录,免得日后又忘记了。
函数模板
函数模板的定义比较的简单,类型如下所示,这里就举一个加法的例子:
template<typename T>
T MyAdd(T a, T b)
{
return a+b;
}
函数模板在使用的时候需要进行实例化,根据具体的数据生成不同的模板函数,与普通函数的调用类似。但是模板函数与普通函数不同的是不允许自动类型转化,比如
int a=1, b=2;
double c=0;
MyAdd(a, b); //3
MyAdd(a, c); //error
a和c的数据类型不同,导致了该函数的调用出错了。除此之外,函数模板还有以下的特点:
1、可以被重载
2、如果有普通的函数与它相同,数据类型普通函数可以使用,会优先考虑普通函数
以上的内容在《c++ primer》中也有提到,但是模板是如何编译使用的呢?这里首先需要介绍以下cpp文件编译成可执行文件的过程。
cpp编译过程概况
其实这过程我也不是很了解,只能说一个大概,秉持着知其然可以不知其所以然的态度,所以自己大概的理解就行了吧,一个cpp文件的编译过程可以如下图所示。
可以通过下面的指令看生成的文件,比如说我的目标cpp文件是
index.cpp
g++ -E index.cpp -o index.i
g++ -S index.cpp -o index.s
g++ -C index.s -o index.o
g++ index.s -o index
在linux中可执行文件没有后缀。可以看到cpp文件刚开始就是单独编译,之后再链接其他库文件。
模板函数的编译
编译过程如上述所示,但是模板函数除了本身编译之外,他会按照具体的调用情况再编译一次,一种类型算作一个,这里可以看.s文件来看到底编译了几种类型的模板函数。举个例子:
普通一个代码如下所示:
#include <iostream>
template<typename T>
T myadd(T a, T b)
{
return a+b;
}
int main()
{
int a=1, b=2, e=8, f=9;
double c = 4.8, d = 8.9;
myadd(a, b);
myadd(e, f);
myadd(c, d);
return 0;
}
之后可以看编译得到的.s文件,因为内容过多,就不都粘贴了,可以看main函数中有三个call
,其中前两个是一样的,与最后一个不同,这也说明了如果是同一个类型只需要生成一个实例就行了。
call _Z5myaddIiET_S0_S0_
movl -20(%rbp), %edx
movl -24(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call _Z5myaddIiET_S0_S0_
movsd -8(%rbp), %xmm0
movq -16(%rbp), %rax
movapd %xmm0, %xmm1
movq %rax, -40(%rbp)
movsd -40(%rbp), %xmm0
call _Z5myaddIdET_S0_S0_
下面,就需要来说说类模板。
类模板
类模板的定义可以如下表示,在使用类模板的时候必须要显示的声明类型,且在类外定义成员函数的时候,需要在前面加上模板类型如:Myclass<T>::***
。
template <typename T>
class Myclass{
//....
};
虽然类的成员函数可以在外面定义,但是他不能像普通的类一样定义分为一个.h文件和.cpp文件。因为在调用该类的时候cpp文件是分开编译的,到最后链接的时候才从头文件去找,但是如果是类用cpp定义的话在编译的就会出错了。
所以,在使用类模板的时候,后缀一般是使用.hpp
,这样就不会出错了。
总结
这个也是自己在学习的过程中发现的一个问题,解释的可能不是很清楚,如果有错误的话,敬请批评指正,菜菜需要指导。