类模板机制

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实例有友元关系。

 

posted @ 2020-05-24 09:39  _yanghh  阅读(292)  评论(0编辑  收藏  举报