以前在学校C++模板用的比较少,碰到的问题也就少。
而在工作中模板的使用随处可见,在遇到问题中学习,也就对模板有了新的认识和理解。
下面是一个简单的小结。
模板本身不是类或函数
首先这一点是需要最先明确的,之前就是没有理解这一点,所以对模板的认识一直停留在表明。
我们借助以下例子来理解这一个点:
template <typename T>
class AutoList
{
public:
AutoList() {}
~AutoList() {}
bool getAutoList() {
return true;}
private:
T value;
};
上面我们定义了一个类模板,但是它不是类——AutoList并不是一个类类型,而是一个类模板。
因此你如果写出以下代码,编译器将会拒绝:
int main()
{
AutoList myList;
return 0;
}
In function ‘int main()’:
错误:missing template arguments before ‘myList’
错误:expected ‘;’ before ‘myList'
你必须为AutoList提供一个模板参数,使它实例化成为一个类型:
int main()
{
AutoList<int> intList;
AutoList<long> longList;
return 0;
}
上面我们分别传入了两个参数int和long,于是类模板将通过实例化产生了两个独立的类型。
你可以理解为,上面代码经过编译器处理之后,变成了如下代码:
class AutoList<int>
{
public:
AutoList() {}
~AutoList() {}
bool getAutoList() {
return true;}
private:
int value;
};
class AutoList<long>
{
public:
AutoList() {}
~AutoList() {}
bool getAutoList() {
return true;}
private:
long value;
};
int main()
{
AutoList<int> intList;
AutoList<long> longList;
return 0;
}
举个简单的例子:你在做蛋糕的时候,类模板AutoList就好比提供给编译器的一个模子,而实例化后的AutoList< int >就好比蛋糕类型。
模子它本身并不属于蛋糕类型,你需要把各种做蛋糕的材料填到模子里面去,才能得到一个实实在在的蛋糕,而这些蛋糕,根据你加入的材料不同,会有不同的口味——草莓味的、巧克力味的、牛奶味的——这就是实例化的过程。
也许以上例子不是特别恰当,但是还是非常直观的。
回到我们的例子,编译器在编译以上代码的过程中,碰到AutoList< int >,于是用int去实例化类模板AutoList,得到一个类型AutoList< int >,然后又碰到AutoList< long >,于是用long去实例化类模板AutoList,得到一个类型AutoList< long >,两个类AutoList< int >和AutoList< long >各有一份代码。之后如果又遇到AutoList< int >,因为已经有一份该类的定义代码了,所以直接用就可以了。在编译器编译完成之后,类模板也就没用了,说不定在最后生成的代码中,类模板已经被丢弃,只剩下一个个根据模板实例化来的类类型。
总结一下:每次实例化,编译器根据传入的模板参数来实例化模板,生成一份新的代码。
模板的这种实例化行为,带来的一个问题就是——代码膨胀。
一开始你以为只有一份代码,可是事实上,你实例化了多少个类型,就有多少份类似的代码——只是会用具体的参数来替换掉T。
模板函数声明为inline
// ok
template <typename T> inline T test(const T& value);
// error
inline template <typename T> T test(const T& value);
模板实参推断
一般情况下,模板实参推断的过程中不会进行隐式类型转换来匹配已有的实例,而是会生成新的实例。
看以下两个例子:
template <typename T>
bool