本小节回顾的知识点分别是成员函数模板,显式实例化,声明。
今天总结的知识分为以下3个点:
(1)成员函数模板
(2)普通类and模板类的成员函数模板
(3)模板显式实例化,模板声明
(1)成员函数模板:
不论是普通类,亦或是模板类,它的成员函数都可以成为一个函数模板。so,若一个类的成员函数是函数模板的话,那该函数模板就称之为“成员函数模板”。
(注意:虚函数不可以成为模板!)
class Person {
public:
//...
template<typename T>
virtual void func(T t) {//错误!函数模板的声明中不允许使用virtual!
cout << t << endl;
}
//...
};
运行结果:
(2)普通类and模板类的成员函数模板
a)普通类的成员函数模板:
(一个普通类,其某个成员函数是函数模板)
请看以下例子codes:
class A {普通类
public:
template<typename T>
void mytfunc(T tmpt) {//成员函数模板 ==> 普通类的成员函数是一个函数模板
cout << tmpt << endl;
}
};
int main(void) {
A a;
a.mytfunc(3);//注意:只有当编译器遇到这句代码时,才会实例化出特殊版本的成员函数
/*
==> 此时编译器为我们生成了一个特殊版本的成员函数myfunc:
void mytfunc(int tmpt){
cout<< tmpt <<endl;
}
(注意:这里是编译器自动推断我们传入的参数是一个int类型)
*/
//==> result: 3
a.mytfunc<double>(1388.8);
/*
==> 此时编译器为我们生成了一个特殊版本的成员函数myfunc:
void mytfunc(double tmpt){
cout<< tmpt <<endl;
}
(注意:这里是我们给编译器指定了参数是一个double类型)
*/
//==> result: 1388.8
return 0;
}
a)模板类的成员函数模板:
(一个模板类, 其某个成员函数是函数模板)
注意①:当need在模板类的定义之外部去定义其成员函数模板时,应该这么写:
格式:
template<整个模板类的模板参数列表1>
template<某个成员函数模板的模板参数列表2>
retName className<列表1>::funcName(Params){
//...
}
注意②:类模板的成员函数(包括普通成员函数and成员函数模板)只有被调用时(也即程序中出现了对该成员函数/函数模板的调用代码时),编译器才会帮我们把这些函数的具体实现代码进行实例化。若模板类中的某函数在程序中从未被调用过,那么编译器当然不会实例化(生成)该成员函数的具体代码了。
请看以下例子codes:
template<typename C>
class AA {
public:
C m_ic;
public:
//定义一个构造函数模板
template<typename T1,typename T2>//注意:模板参数T1和T2与整个类的模板参数C是没有关系的!
AA(T1 v1, T2 v2);
template<typename T3, typename T4>
void func(T3 v3, T4 v4);
template<typename T5>
void func2(T5 tmpt){ cout<<"this is func2!"<<endl; }
void func3(){ cout<<"this is func3!"<<endl; }
};
template<typename C> //先写整个类模板的参数列表
template<typename T1, typename T2>//再写这个类内的成员函数模板的参数列表
AA<C>::AA(T1 v1, T2 v2) {
cout << "this is constructor ~ yeah" << endl;
}
template<typename C> //先写整个类模板的参数列表
template<typename T3, typename T4> //再写这个类内的成员函数模板的参数列表
void AA<C>::func(T3 v3, T4 v4) {
cout << "v3 = " << v3 << " v4 = " << v4 << endl;
}
int main(void){
AA<float> aa(1,2);
//由于构造函数是编译器自动为我们调用的(并且为我们自动推断出传入构造函数的参数是2个int型参数)
//因此我们必然不能 给构造函数指定模板参数进而调用它
aa.func(3, 4);
aa.func<double,double>(33.3, 44.4);//但是对于一般的成员函数,我们是可以指定模板参数来调用它的!
return 0;
}
解释:上述代码中,模板类AA中的成员函数只有其构造还有func()函数给调用了,也即此时编译器为我们实例化了这2个成员函数的具体实现代码:
AA的构造函数中的代码:cout << "this is constructor ~ yeah" << endl;被编译器实例化出来了
func()中的代码:cout << "v3 = " << v3 << " v4 = " << v4 << endl;被编译器实例化出来了
,而func2()和func3()这2个函数并没有被编译器实例化:
func2()中的代码:{ cout<<"this is func3!"<<endl; }没有被编译器实例化!
func3()中的代码:{ cout<<"this is func2!"<<endl; }没有被编译器实例化!
(3)模板显式实例化,模板声明:(了解认识一下即可)
为了防止在多个.cpp源文件中都实例化相同的 类模板(生成多个类模板实例的动作会使得build时的成本消耗极大),C++11中提出了一个deal方法,称之为“显式实例化”。用以减少系统实例化模板类/函数的开销!
显式实例化的格式:
第一步(在其中一个.cpp源文件中进行"实例化定义")
"实例化定义":提前在一个.cpp源文件中实例化好你所要用到的模板类/函数的代码
template retName tfunc(realTemplateParams);//实例化定义模板函数
template tclassName<realTemplateParams>;//实例化定义模板类
第二步(在其他.cpp源文件中都进行"实例化声明")(extern告诉编译器本.cpp的外部是存在一个"实例化定义"的)
"实例化声明":告诉编译器本.cpp的外部存在了一个已经实例化好的模板类/函数的代码,因此我本.cpp源文件就不需要再实例化了,直接拿来用即可~(拿来吧你)
extern template retName tfunc(realTemplateParams);//实例化声明模板函数
extern template tclassName<realTemplateParams>;//实例化声明模板类
注意①:当编译器一看到该显式实例化的代码时,就会为我们实例化出一个特定版本的模板类/模板函数。
比如:
template void mytfunc(int tmpt1, int tmpt2);
//此时编译器一看到这条代码就会为我们生成一个void mytfunc(int tmpt1, int tmpt2)带有2个int形参版本模板的函数
template AA<float>;//此时编译器一看到这条代码就会为我们生成一个AA<float>类
注意②:显式实例化是一带多的关系,即:每一个实例化定义都可以带有多个实例化声明。
解释:这样的实例化定义的代码只需要在其中一个.cpp源文件中用到即可了,其他要用到的.cpp源文件就用extern实例化声明即可。并不需要你在all的.cpp源文件中都写一条这样的代码。除非你想生成其他特定类型的模板类,那你大可再按照需求来写对应的显式实例化的代码。
比如:
在test_template.h中有这样一个类模板AA和模板函数mytfunc():
#ifndef __TEST_TEMPLATE_H__
#define __TEST_TEMPLATE_H__
#include<iostream>
using namespace std;
//模板类
template<typename C>
class AA {
public:
C m_ic;
public:
//定义一个构造函数模板
template<typename T1, typename T2>//注意:模板参数T1和T2与整个类的模板参数C是没有关系的!
AA(T1 v1, T2 v2);
template<typename T3, typename T4>
void func(T3 v3, T4 v4);
};
template<typename C> //先写整个类模板的参数列表
template<typename T1, typename T2>//再写这个类内的成员函数模板的参数列表
AA<C>::AA(T1 v1, T2 v2) {
cout << "this is constructor ~ yeah" << endl;
}
template<typename C> //先写整个类模板的参数列表
template<typename T3, typename T4> //再写这个类内的成员函数模板的参数列表
void AA<C>::func(T3 v3, T4 v4) {
cout << "v3 = " << v3 << " v4 = " << v4 << endl;
}
//模板函数
template<typename T1, typename T2>
void mytfunc(T1 tmpt1, T2 tmpt2) {
cout <<"add "<< tmpt1 <<" of "<< tmpt2 <<" is "<< tmpt1+ tmpt2<< endl;
}
#endif //__TEST_TEMPLATE_H__
在main1.cpp中有这样一个调用类模板生成特定对象的函数:
#include<iostream>
#include"test_template.h"
using namespace std;
void tfunc() {
AA<float> a(1, 1);
}
在main2.cpp中也有这样一个调用类模板生成特定对象的函数:
#include<iostream>
#include"test_template.h"
using namespace std;
int main(void) {
AA<float> aa(1,2);
aa.func(3, 4);
aa.func<double,double>(33.3, 44.4);
return 0;
}
从而,这样就会在多个.cpp源文件中都实例化了同一个类模板!这样会导致编译开销变大!
so,请看以下显式实例化的代码:
main1.cpp中添加这样一条显式"实例化定义"的代码:
#include<iostream>
#include"test_template.h"
using namespace std;
template void mytfunc(int tmpt1, int tmpt2);
template AA<float>;
void tfunc() {
AA<float> a(1, 1);
}
main2.cpp中添加这样一条extern显式"实例化声明"的代码(因为main函数要用到AA<float>这种特定版本的类!so要实例化声明一下,否则就不需要):
#include<iostream>
#include"test_template.h"
using namespace std;
extern template void mytfunc(int tmpt1, int tmpt2);
extern template AA<float>;
int main(void) {
AA<float> aa(1,2);
aa.func(3, 4);
aa.func<double,double>(33.3, 44.4);
return 0;
}
这样,在用到AA<float>这种特殊版本的类之前,编译器就会实例化该特殊版本的类的代码,进而后面需要用到时就不会再重新实例化了,在一个.cpp源文件中实例化了AA<float>之后,另外的.cpp源文件也就不再需要实例化一次了,这样就提高了你代码的build效率!
以后在读别人写的代码时,遇到extern template AA<float>;还有template AA<float>;类似的显式实例化的代码时千万不要发懵就行哈~
以上就是我总结的关于成员函数模板,显式实例化,声明的笔记。希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~