C++template 包含模型

       我们可以用几种方法来组织模板源代码。这一节将给出最常用的方法:包含模型(inclusion model)。
1.链接器错误
       类(class)和其他类型(other type)都被放在一个头文件中。通常而方,头文件是一个扩展名为.hpp(或者.H, .h, .hh, .hxx)的文件。
        对于全局变量和(非内联)函数,只有声明放在头文件中,定义则位于dot-C文件。通常而言,
dot-C文件是指扩展名为.cpp(或者.C,.c, .cc, .cxx)的文件。
       这样一切都可以正常运作了。所需的类型定义在整个程序中都是可见的;并且对于变量和函数而言,链接器也不会给出重复定义的错误。
      当牢记了这种约定之后,刚开始接触模板的程序员却总会对这种约定发出抱怨,因为它令链接器产生了一个错误。我们可以通常过下面的(错误)小程序来说明这一点。利用上面针对普通代码的约定,我们应该在一个头文件中声明模板:
//myfirst.h

#ifndef MYFIRST_H
#define MYFIRST_H

//模板声明
template 
void print_typeof(T const &x);

#endif // MYFIRST_H


print_typeof()是一个辅助函数模板的声明,它输出某些类型信息。该函数模板的实现被放在下面的doc-C文件里面:

//myfirst.cpp

#include 
#include 
#include "myfirst.h"

//模板的实现、定义
template 
void print_typeof(T const &x)
{
    std::cout << typeif(x).name() << std::endl;
}


      这个例子使用typeid运算符来输出一个字符串,它描述了作为参数传递的表达式的类型(见5.6节)。
      最后,我们在另一个dot-C文件里使用这个模板,并且把模板声明包含进这个文件:
//myfirstmain.cpp

#include "myfirst.h"
//使用模板
int main()
{
    double ice = 3.0;
    print_typeof(ice);//调用参数类型为double的函数模板
    return 0;
}


       大多数C++编译器都会顺利地接受这个程序;但是链接器可能会报错,提示找不到函数print_typeof()的定义。


       事实上,这个错误的原因在于:函数模板print_typeof()的定义还没有被实例化。为了使模板真正得到实例化,编译器必须知道:应该实例化哪个定义以及要基于哪个模板实参来进行实例化。遗憾的是,在前面的例子里,这两部分信息位于分开编译的不同文件里面。因此,当我们的编译器看到print_typeof()调用,但还没有看到基于double实例化的函数定义的时候,它只是假设在别处提供了这个定义,并产生一个指向该定义的引用(让链接器利用该引用来解决这个问题)。另一方面,当编译器处理文件myfirst.cpp的时候,它前没有指出:编译器必须基于特定实参对所包含的模板定义进行实例化。

2.头文件中的模板
       对于前面的问题,我们通常是采取对待宏或内联函数的解决办法:我们把模板的定义也包含在声明模板的头文件里面,即让定义和声明都位于同一个头文件中。对于上面的例子,我们可以通过把:
#include "myfirst.cpp"添加到myfirst.h的末尾,或者在每个使用模板的dot-C文件都包含myfirst.cpp。显然,第3种方法就是完全不要myfirst.cpp,然后重写myfirst.h,让它同时包含模板声明和模板定义:
//myfirst2.h

#ifndef MYFIRST2_H
#define MYFIRST2_H

#include 
#include 

//模板声明
template 
void print_typeof(T const &x);

//模板的实现、定义
template 
void print_typeof(T const &x)
{
    std::cout << "typeid(x).name()=====" << typeid(x).name() << std::endl;
}

#endif // MYFIRST2_H
//myfirst2main.cc
#include "myfirst2.h"
//使用模板
int main()
{
    double ice = 3.0;
    print_typeof(ice);//调用参数类型为double的函数模板
    return 0;
}

      我们称模板的这种组织方式为包含模型。通过使用这种模型,你会发现前面的程序可以顺利编译、链接和运行。

      针对这一点,我们可以得出一些结论:包含模型明显增加了包含头文件myfirst.h的开销,这也正是包含模型最大的不足之处。在例子中,主要的开销并不是取决于模板定义本身的大小,而在于模板定义中所包含的那些头文件(在我们的例子中是)的大小。你或许已经知道这样会带来成千上万行的代码,因为每个诸如的头文件本身也都包含了许多类似的模板定义。
      在实际应用中,这是一个很严重的问题,因为它大大增加了编译复杂程序所耗费的时间。因此我们将在后面几节给出几种可能的解决方法。然而,现在的程序大多已经不需要在编译和链接上面花上几个小时,将来就更不用说了。
      如果不需要考虑创建期的时间问题,我们建议你尽量使用包含模型来组织模板代码。我们在后面会考察另外两种组织模板的方式,但就我们的观点看来,另外两种组织方式的实际缺陷往往比这里所讨论的创建期开销更加严重。当然,这两种组织方式也有其他一些与软件开发的应用方面间接相关的优点。
      从包含模型得出的另一个(更微妙的)结论是:非内联函数模板与“内联函数和宏”有一个很重要的区别,那就是非内联函数模板在调用的位置并不会被扩展,而是当它们基于某种类型进行实例化之后,才产生一份新的(基于该类型的)函数拷贝。因为这(产生函数拷贝)是一个自动化过程,所以在编译结束的时候,编译器可能会在不同
的文件里产生两份拷贝,于是,当链接器发现同一个函数具有两种不同的定义时,就会报告一个错误。理论上讲,这并不是我们需要关心的问题,它应该由C++的编译系统来解决。而且,事实上大多数情况下都不会出现这种问题,我们根本没有必要太过于在意这个问题。但对于需要创建自身代码库的大项目,我们就要充分注意这个问题。我们将在第10章详细讨论C++的实例化机制;仔细学习C++翻译系统(或者编译器)所附带的随机文档也有助于理解这个问题。
       最后,我们需要指出的是:在我们的例子中应用到普通函数模板的所有特性,对类模板的成员函数和静态数据成员,成员函数模板也都是适用的。

你可能感兴趣的:(C++,template学习笔记)