7.3模板

模板可以使C++类支持通用编程技术。模板可以看作是一种强大的宏。当一个模板类或者函数用于特定的类或者类型,例如float或者int,相应的模板代码会编译为对应的类型。

7.3.1使用C++标准库模板

C++标准库’libstdc++’配合GCC提供了一个广泛的通用容器类例如链表和队列用于常用的算法例如排序。这些类早先是标准模板库(STL)的一部分,但是现在包含在了C++标准库中。

下面的程序使用模板list<string>通过创建一个字符串链表说明了模板库的使用方法:

include <list>

include <string>

include <iostream>

using namespace std;

int

main ()

{

list<string> list;

list.push_back("Hello");

list.push_back("World");

cout << "List size = " << list.size() << endl;

return 0;

}

使用标准库中的模板类则不需要其他特殊选项编译程序;编译此程序的命令行选项和上面的一样:

$ g++ -Wall string.cc

$ ./a.out

List size = 2

注意使用g++生成的可执行文件,如果使用了C++标准库则共享库’libstdc++’会被链接进去这是GCC默认支持的。这个库有几个版本--如果你发布的程序使用了C++标准库,你需要确保’libstdc++’的版本合适或者使用命令行选项’-static’静态链接你的程序。

7.3.2支持你自己的模板

除了C++标准库提供的模板类你可以定义你自己的模板。使用模板的推荐方法是遵循inclusion compilation model(包含编译模型)--将模板定义在头文件中。这是C++标准库使用的方法用于支持GCC编译器。这个头文件可以使用’#include’将它包含在需要模板的源代码中。

例如下面的模板文件创建了一个简单的Buffer<T>类,它使用目标类型T代表了一个缓存块。

ifndef BUFFER_H

define BUFFER_H

template

class Buffer

{

public:

Buffer (unsigned int n);

void insert (const T & x);

T get (unsigned int k) const;

private:

unsigned int i;

unsigned int size;

T *pT;

};

template

Buffer<T>::Buffer (unsigned int n)

{

i = 0;

size = n;

pT = new T[n];

};

template

void

Buffer<T>::insert (const T & x)

{

i = (i + 1) % size;

pT[i] = x;

};

template

T

Buffer<T>::get (unsigned int k) const

{

return pT[(i + (size - k)) % size];

};

endif / BUFFER_H /

这个文件同时包含了类的声明和成员函数的定义。这个类仅仅用于示例,不应该在实际编程中使用。注意include guards(守卫)的使用,它用于测试BUFFR_H宏的存在,如果头文件在同一个上下文中多次包含则头文件中的定义只需要解析一次。

下面的程序使用了模板Buffer类创建了一个大小为10的缓存块,用于排序浮点数0.25和1.0:

include <iostream>

include "buffer.h"

using namespace std;

int

main ()

{

Buffer<float> f(10);

f.insert (0.25);

f.insert (1.0 + f.get(0));

cout << "stored value = " << f.get(0) << endl;

return 0;

}

在使用模板类和函数之前用#include "buffer.h"将其包含到源文件中。然后可以使用下面的命令编译此程序:

$ g++ -Wall tprog.cc

$ ./a.out

stored value = 1.25

源文件中使用了模板函数的地方,g++会编译相应的定义在头文件中的函数并将编译后的代码放到目标文件中合适的位置。

如果模板函数在程序中使用了多次则它会存放在多个目标文件中。GNU链接器会确保最终的可执行文件中只有一份拷贝。当其他链接器遇到多个模板函数的拷贝时可能会报告”multiply defined symbol”错误--使用这些链接器链接程序的方法将在下面讨论。

7.3.3具体的模板实例

为了实现模板编译的完全控制,g++可以使用’-fno-implicit-templates’选项使模板的每次使用都有具体实例。使用GNU链接器时不需要使用这种方法--这是一种可选的编译模型用于那些不能消除目标文件中模板函数的重复定义的链接器。

在这种方法下,使用’-fno-implicit-templates’选项时模板函数不在他使用的地方编译。编译器会查找一个具体实例中模板使用的关键字’template’作为一种特定的类型强制编译(这是GNU对标准行为的扩展)。这些实例放置在一个分开的源文件中,这个程序需要的所有模板函数会被统一编译成一个目标文件。

这保证了每个模板在目标文件中只出现一次,并且和那些不能消除重复定义的链接器相互兼容。

例如,下面的’templates.cc’文件包含上面给出的’tprog.cc’程序使用的一个具体的实例Buffer<float>类:

include "buffer.h"

template class Buffer<float>;

整个程序可以使用下面的命令编译和链接:

$ g++ -Wall -fno-implicit-templates -c tprog.cc

$ g++ -Wall -fno-implicit-templates -c templates.cc

$ g++ tprog.o templates.o

$ ./a.out

stored value = 1.25

模板函数的所有目标代码都包含在’templates.o’文件中。当使用’-fno-implicit-templates’选项编译程序时’tprog.o’文件中不包含模板函数代码。

如果程序修改为使用模板函数的其他类型,’templates.cc’文件中可以增加其他具体实例。例如下面的代码为Buffer对象添加了一个实例包含double和int值:

include "buffer.h"

template class Buffer<float>;

template class Buffer<double>;

template class Buffer<int>;

具体实例的缺点是必须知道程序需要的模板类型。对于一个编译过的程序这是非常困难。任何丢失的模板实例可以在链接阶段被检查出来,通过查看哪些函数未定义。然后添加到具体实例列表中。

具体实例也可以用于制作预编译模板函数的库,通过创建一个包含所有程序要求的模板函数实例的目标文件(就像上面的’templates.cc’文件)。例如上面的目标文件包含Buffer类需要的’float’,’double’和’int’类型的模板实例的机器码,并且可以打包为一个库发布出去。

7.3.4 export关键字

在写本书的时候,GCC(GCC 3.3.2)还没有支持C++的新关键字’export’。

这个关键字用于将模板接口从他们的实现中分离。然而它增加了链接的复杂性,在实践中会减少这个关键字的任何有点。

Export关键字没有广泛应用,大多数其他编译器不支持它。讨论包含编译模型有点早,推荐使用更加简单,更加便于移植的方式使用模板。