首页 > 代码库 > C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型
C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型
模板与泛型编程
--模板编译模型
引言:
当编译器看到模板定义的时候,它不立即产生代码。只有在用到模板时,如果调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例。
一般而言,当调用函数时[不是模板],编译器只需看到函数的声明。类似的,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。
模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。当调用函数模板或类模板的成员函数的时候,编译器需要函数定义,需要那些通常放在源文件中的代码。
标准C++为编译模板代码定义了两种模型。在两种模型中,构造程序的方式很大程度上是相同的:类定义和函数声明放在头文件中,而函数定义和成员定义放在源文件中。两种模型的不同在于,编译器怎样使用来自源文件的定义。如本书所述,所有编译器都支持第一种模型,成为“包含”模型,只有一些编译器支持第二种模型,“分别编译”模型。
一、包含编译模型
在包含编译模型中,编译器必须看到用到的所有模板的定义。一般而言,可以通过在声明函数模板或类模板的头文件中添加一条#include指示使定义可用,该#include引入了包含相关定义的源文件:
//in utilities.h #ifndef UTILITIES_H_INCLUDED #define UTILITIES_H_INCLUDED #include "utilities.cpp" template <class T> int compare(const T &,const T &); #endif // UTILITIES_H_INCLUDED
//in utilities.cpp #include "utilities.h" template <class T> int compare(const T &val1,const T &val2) { if (val1 < val2) return -1; if (val2 < val1) return 1; return 0; }
这一策略使我们能够保持头文件和实现文件的分离,但是需要保证编译器在使用模板的代码时能看到两种文件。
某些使用包含模型的编译器,特别是较老的编译器,可以产生多个实例。如果两个或多个单独编译的源文件使用同一模板,这些编译器将为每个文件中的模板产生一个实例。通常,这种方法意味着给定模板将实例化超过一次。在链接的时候,或者在预链接阶段,编译器会选择一个实例化而丢弃其他的。在这种情况下,如果有许多实例化同一模板的文件,编译时性能会显著降低。对许多应用程序而言,这种编译时性能降低不大可能在现代计算机上成为问题,但是,在大系统环境中,编译时选择问题可能变得非常重要。
这种编译器通常支持某些机制,避免同一模板的多个实例化中隐含的编译进开销。编译器优化编译时性能的方法各不相同。如果使用模板的程序的编译时间难于承担,请查阅编译器的用户指南,看看你的编译器能提供什么支持以避免多余的实例化。
二、分别编译模型
在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器知道要记住给定的模板定义,可以使用export关键字来做这件事。
export关键字能够指明给定的定义可能会需要在其他文件中产生实例化。在一个程序中,一个模板只能定义为导出一次。编译器在需要产生这些实例化时计算出怎样定位模板定义。export关键字不必在模板声明中出现。
一般我们在函数模板的定义中指明函数模板为导出的,这是通过在关键字template之前包含export关键字而实现的:
//in utilities.h export template <typename Type> int compare(const Type &val1,const Type &val2) /***/
这个函数模板的声明像通常一样应放在头文件中,声明不必指定export。
对类模板使用export更复杂一些。通常,类声明必须放在头文件中,头文件中的类定义体不应该使用关键字export,如果在头文件中使用了export,则该头文件只能被程序中的一个源文件使用。
相反,应该在类的实现文件中使用export:
// in header file template <class Type> class Queue { ... };
// in implementation file export template <class Type> class Queue; #include "Queue.h" //...
导出类的成员将自动声明为导出的。也可以将类模板的个别成员声明为导出的,在这种情况下,关键字export不在类模板本身指定,而是只在被导出的特定成员定义上指定。导出成员函数的定义不必在使用成员时可见。任意非导出成员的定义必须像在包含模型中一样对待:定义应放在定义类模板的头文件中。
//P544 习题16.27 //in middle.h #ifndef MIDDLE_H_INCLUDED #define MIDDLE_H_INCLUDED #include <vector> #include <algorithm> using namespace std; template <typename Type> bool middle(const vector<Type> &,Type &); #include "middle.cpp" #endif // MIDDLE_H_INCLUDED
//in middle.cpp #include "middle.h" template <typename Type> bool middle(const vector<Type> &vec,Type &val) { vector<Type> tmp(vec); sort(tmp.begin(),tmp.end()); if (tmp.size() % 2 == 0) { return false; } typename vector<Type>::iterator index = tmp.begin() + tmp.size()/2; if (*index > *(index -1) && *(index) < *(index + 1)) { val = *index; return true; } return false; }
//in main.cpp #include <iostream> #include "middle.h" using namespace std; int main() { int ia[] = {1,2,3,4,5,6,7}; int ai[] = {1,2,3,4,5,6}; vector<int> ivec1(ia,ia + 7),ivec2(ai,ai + 6); int val; if (middle(ivec1,val)) { cout << "Middle: " << val << endl; } else { cout << "No Middle!" << endl; } if (middle(ivec2,val)) { cout << "Middle: " << val << endl; } else { cout << "No Middle!" << endl; } } /**注意:g++编译器支持包含模型 *但是不能将模板的实现文件包含到project中, *否则会引起编译错误! */