目录
vs 2013为了尽可能减少代码膨胀,编译器会按需实例化,调用成员函数
一.非类型模板参数
模板参数分类 : 类型形参与非类型形参。
类型形参: 即出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用.(常用整形)
1.非类型模板参数
template<class T, size_t N> //N:非类型模板参数
class Array
{
private:
T _a[N]; //利用非类型模板参数指定数组的大小
};
int main()
{
Array<int,100> a1;
Array<char,1000> a2;
return 0;
}
2.模板也可以缺省
template<class T = int, size_t N = 10> //缺省值
class Array
{
private:
T _a[N];
};
int main()
{
Array<> a1;
return 0;
}
3.N不能被修改
template<class T = int, size_t N = 10>
class Array
{
public:
void f()
{
N = 1000; //不行,会报错
}
private:
T _a[N];
};
int main()
{
Array<int,100> a1;
return 0;
}
为什么①不报错,②报错:类模板是按需编译,只要里面没有调用就不会实例化如果a1没有调用f(就不会报错.
二.模板的特化
1.为什么需要特化
有时候,编译默认函数模板或者类模板,在一些特殊场景下,不能正确处理需要的逻辑,需要针对—些情况进行特殊化处理,就要做模板的特化
例如:
template<class T>
bool IsEqual(T& left, T& right)
{
return left == right;
}
void Test()
{
char p1[] = "hello";
char p2[] = "hello";
bool ret1 = IsEqual(1,1); // ok true
bool ret2 = IsEqual(1,1 , 2,2); //ok false
bool ret3 = IsEqual(p1 , p2); // false ?
ret3 的返回值是false,因为这里比较的是两个地址,p1,p2是两个指针存在栈上指向的内容虽然相同但是在不同的存储空间;但是我们想要比较的是里面的内容是否相同。
对于上述实例,使用模板可以实现一些与类型无关的代码,但对于一些特殊的类型可能会得到一些错误的结果,此时就需要对模板进行特化,即在原模板的基础上,针对特殊类型进行特殊化的实现方式。
2.函数模板特化(对指定类型进行特殊化处理)
函数模板的特化步骤:①. 必须要先有一个基础的函数模板②. 关键字template后面接一对空的尖括号<>③. 函数名后跟一对尖括号,尖括号中指定需要特化的类型④. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
//基础的函数模板
template<class T>
bool IsEqual(T& x, T& y)
{
return x == y;
}
//对于char*类型的特化
template<>
bool IsEqual<char*>(char*& x, char*& y)
{
return strcmp(x, y) == 0;
}
//也算一种特化方式
bool IsEqual(char*& left, char*& right)
{
return strcmp(left,right) == 0;
}
3.类模板的特化 (特化的本质:显示指定实例化模板)
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
①全特化:将模板参数列表中所有的参数都确定化。
template<> //全特化
class Data<int, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
void TestVector()
{
Data<int, int> d1;
Data<int, char> d2; //调用特化版本
}
②偏特化 :任何针对模板参数进一步进行条件限制设计的特化版本
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
- 部分特化 :将模板参数类表中的一部分参数特化
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
int main()
{
Data<int,int> d1; //调用特化的int
Data<int,double> d2;
return 0;
}
- 参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本
//两个参数偏特化为指针类型
template <class T1, class T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <class T1, class T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
void test ()
{
Data<double , int> d1; // 调用特化的int版本
Data<int , double> d2; // 调用基础的模板
Data<int*, int*> d3; // 调用特化的指针版本
Data<int&, int&> d4(1, 2); // 调用特化的引用版本
}
三.模板的分离和编译
1.概念
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
2.为什么不能分离编译
C/C++程序运行需要以下几个步骤:
- 预处理: 头文件展开、去注释、宏替换、条件编译等。
- 编译:对程序按照语言特性进行词法、语法、语义分析,错误检查无误后生成汇编代码,注意头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的。
- 汇编: 把编译阶段生成的文件转成目标文件(生成二进制的机器码)。
- 链接: 将生成的各个目标文件进行链接(并处理没有解决的地址问题),生成可执行文件。
(1)前面的预处理、编译和汇编都没有问题,现在就需要将生成的两个目标文件进行链接操作了,但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义,主要原因是函数模板并没有生成对应的函数 , 声明在头文件,定义在.cpp文件,无法进行实例化。( 在编译器编译阶段,对于模板函数的使用, 编译器需要根据传入的实参类型来推演生成对应类型的函数 以供调用)。因为在全过程中函数模板的模板参数T都没有被实例化,所以函数模板根本就不知道该实例化T为何类型的函数。
(2)函数模板或类模板只有在编译时, 才会生成一个真正的实体(模板函数或模板类), 这个过程在编译时完成, 而 分离编译时多文件各自编译各自的, 所以在.cpp中的模板定义在编译时并不知道到底要实例化出一个怎样的模板函数或模板类, 所以就不会实例化。 当在链接阶段, 需要链接这个模板函数或模板类时就会报错。
3.解决方法
①模板就不要将声明和定义分离了,直接放在一个.hpp / .h文件中 (用的地方有定义) (推荐)
②显示实例化: 在Test.cpp中显示实例化(麻烦)
template
int Add<int>(const int& left, const int& right);
四.模板总结
优点:
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
- 增强了代码的灵活性。
缺陷:
- 模板会导致代码膨胀问题,也会导致编译时间变长。(看起来节省了代码,但是实际中存在代码膨胀问题,按需实例化)
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
补充 : 分离编译拓展阅读
1.vs 2013为了尽可能减少代码膨胀,编译器会按需实例化,调用成员函数
2.正确的模板声明
template<typeaname T1, typename T2, size_t N>
template<typeaname T, size_t N=100, class _A=alloc<T>>
template<size_t N>