类模板机制
1. 基本概念
是对一批仅仅成员数据类型不同的类的抽象,程序员只要为这一批类所组成的整个类家族创建一个类模板,给出一套程序代码,就可以用来生成多种具体的类。
总结以下两点:
1)类模板用于实现类所需数据的类型参数化。
2)类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。
类模板基本语法举例:
// 参数化一个类型T,允许参数化多个类型
template<typename T>
class A
{
public:
A(T t) { this->t = t; }
T &getT() { return t; }
public:
T t;
};
// 子类从模板类继承的时候,既可以派生类模板,也可以派生非模板类
// 1. 可以从类模板派生出非模板类,在派生中,作为非模板类的基类,必须是类模板实例化后的模板类
class B : public A<int>
{
public:
B(int i) : A<int>(i) {}
void printB() { cout << "A:" << t << endl; }
};
// 2. 从类模板派生类模板可以从类模板派生出新的类模板
template <class T>
class C : public A<T>
{
public:
C(T t) : A<T>(t) {}
};
int main()
{
A<int> a(100); // 需要提供类型参数
int x = a.getT();
B b(10);
b.printB();
C<int> c(10);
return 0;
}
2. 模板的编译过程
什么是编译单元:一个编译单元(translation unit)是指一个.cpp文件以及它所#include的所有.h文件,.h文件里的代码将会被扩展到包含它的.cpp文件里,
然后编译器编译该.cpp文件为一个.obj文件,并且本身包含的就已经是二进制码,但是不一定能够执行,因为并不保证其中一定有main函数。
什么是分离式编译:一个项目由若干个源文件共同实现,而每个源文件(.cpp)单独编译成目标文件(.obj),最后将所有目标文件连接起来形成单一的可执行文件的过程。
下面举一个例子来说明:
test.h文件内容如下
void func(); // 声明一个函数 func
test.cpp文件内容如下:
#include "test.h"
//这里实现出 test.h 中声明的 func 函数
void func()
{
… // do something
}
main.cpp文件内容如下:
#include "test.h"
int main()
{
func(); // 调用func,func具有外部连接类型
return 0;
}
说明:test. cpp和main.cpp各自被编译成不同的.obj文件,在main.cpp中,调用了func函数,然而当编译器编译main.cpp时,它仅仅知道的只是
main.cpp中所包含的test.h文件中的一个关于void func();的声明,所以,编译器将这里的f看作外部连接类型,func的实现代码实际存在
于test.cpp所编译成的test.obj中。在main.obj中对f的调用只会生成一行call指令,链接器负责在其它的.obj中寻找func的实现代码,找
到以后将call func这个指令的调用地址换成实际的func的函数进入点地址。
然而,对于模板,模板函数的代码其实并不能直接编译成二进制代码,其中要有一个“实例化”的过程。举个例子(将模板的声明和实现分离):
test.h文件内容如下:
template<class T>
class A
{
public:
void func(); // 这里只是个声明
};
test.cpp文件内容如下:
#include "test.h"
template<class T>
void A<T>::func() // 模板的实现
{
…//do something
}
main.cpp文件内容如下:
#include "test.h"
int main()
{
A<int> a;
a.func(); // #1
return 0;
}
说明:编译器在#1处并不知道A<int>::f的定义,因为它不在test.h里面,于是编译器只好寄希望于链接器,希望它能够在其他.obj里面找到A<int>::func的实例,
在本例中就是test.obj,然而,后者中真有A<int>::func的二进制代码吗?NO!!!因为C++标准明确表示,当一个模板不被用到的时侯它就不该被实例化出来,
test.cpp中用到了A<int>::func了吗?没有!!所以实际上test.cpp编译出来的test.obj文件中关于A::f一行二进制代码也没有,于是链接器就傻眼了,只好给
出一个链接错误。但是,如果在test.cpp中写一个函数,其中调用A<int>::func,则编译器会将其实例化出来,链接器就能够完成任务。
模板的二次编译:
1)非模板类在编译的时候就会被实例化出代码,但编译模板类则不是如此,C++标准明确表示当一个模板不被用到的时侯它就不该被实例化出来。
2)当编译到用模板类特例定义对象的代码时,如A<int> a; 此时编译器才会生成对应实例化类的二进制代码,即第二次编译。
3)第二次编译的时候如果该编译单元能访问到模板类的实现代码,则好说,否则只能等到链接,如果其它模块也没有实例化过该代码,则会链接出错。
解决办法:
1)模板类声明在test.h中,定义在main.cpp中,调用在main.cpp中,则能运行成功。
2)模板类声明在test.h中,定义在test.h中,调用在main.cpp中,则能运行成功,因为在预处理阶段会对头文件展开。
这里抛出一个问题:假如多个.cpp都定义了相同类型的对象,那编译器会在每个编译单元都会产生相同的代码吗?
答案:NO,编译器肯定不会那么蠢的,具体实现细节暂时不表。
3. 类模板中的友元函数
1)普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。
template<class T>
class A
{
friend void func();
friend class B;
private:
T _a;
};
void func()
{
A<int> a1; // func()函数内可访问类A的任意实例
A<double> a2;
a1._a = 1;
a2._a = 1.0;
cout << a1._a << endl;
cout << a2._a << endl;
}
func可访问A任意类实例中的私有和保护成员。
2)类模板或函数模板的友元声明,授予对友元所有实例的访问权。
template<typename T>
class A
{
template<class T1>
friend void func();
private:
T _a;
};
template<typename T1>
void func()
{
A<int> a1;
A<double> a2;
a1._a = 1;
a2._a = 1.0;
cout << a1._a << endl;
cout << a2._a << endl;
}
void functest()
{
func<int>(); // func任意实例可访问类A的任意实例
func<double>();
}
我们模板类A中声明了模板函数func,两者拥有各自的模板形参T和T1,两者是互不影响的,所以在
这里对于func的所有实例(如func<int>,func<double>)对于A的所有实例(如A<int>,A<double>)中的私有或保护成员都可以进行访问。
3)只授予对类模板或函数模板的特定实例的访问权的友元声明。
template<typename T>
class A
{
friend void func<int>();
private:
T _a;
};
template<typename T1>
void func()
{
A<int> a1;
A<double> a2;
a1._a = 1;
a2._a = 1.0;
cout << a1._a << endl;
cout << a2._a << endl;
}
void functest()
{
func<int>();
// func<double>();
}
int main()
{
functest();
return 0;
}
在模板类A中我们声明了模板函数func实例化后的fun<int>的友元关系,因此我们在这里仅能够在func<int>中对模板类A中
的所有实例的私有或保护成员具有访问权限,而对于func<double>则不是模板类A的友元函数,不具有访问其实例的私有或保护成员的权限(编译会出错)。
4)对于3)的例子可以做一点修改
template<typename T>
class A
{
friend void func<T>(); // 这里是一个泛化的T类型
private:
T _a;
};
template<typename T1>
void func()
{
A<int> a1;
A<double> a2;
a1._a = 1;
a2._a = 1.0;
cout << a1._a << endl;
cout << a2._a << endl;
}
void functest()
{
func<int>();
func<double>();
}
int main()
{
functest();
return 0;
}
我们发现对于func<int>仅对模板类A的实例A<int>的私有或保护成员具有访问权限,对于A<double>则没有;而对于func<double>则反之。
总之,对于func的实例只对与它的模板实参一致的A实例有友元关系。
浙公网安备 33010602011771号