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中,
*否则会引起编译错误!
*/

C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

时间: 2024-08-25 10:34:02

C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型的相关文章

C++ Primer 学习笔记_77_模板与泛型编程 --实例化

模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实际模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化. 1.类的实例化 当编写Queue<int>qi时,编译器自己主动创建名为Queue<int>的类.实际上,编译器通过又一次编写Queue模板,用类型int取代模板形參的每次出现而创建Queue<int

C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]

模板与泛型编程 --类模板成员[续1] 二.非类型形参的模板实参 template <int hi,int wid> class Screen { public: Screen():screen(hi * wid,'#'), cursor(hi * wid),height(hi),width(wid) {} //.. private: std::string screen; std::string::size_type cursor; std::string::size_type height

C++ Primer 学习笔记_82_模板与泛型编程 --类模板成员[续2]

模板与泛型编程 --类模板成员[续2] 六.完整的Queue类 Queue的完整定义: template <typename Type> class Queue; template <typename Type> ostream &operator<<(ostream &,const Queue<Type> &); template <typename Type> class QueueItem { friend clas

C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模板的定义. 泛型编程与面向对象编程一样,都依赖于某种形式的多态性.面向对象编程中的多态性在执行时应用于存在继承关系的类.我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异.仅仅要使用基类的引用或指针,基类类型或派生类类型的对象就能够使用同样的代码. 在泛型编程中,我们所编写的类和函数能够

C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一些情况下,能够利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数. compare函数和 Queue类都是这一问题的好样例:与C风格字符串一起使用进,它们都不能正确工作. compare函数模板: template <typename Type> int compare(cons

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

模板与泛型编程 --一个泛型句柄类 引言: [小心地雷] 这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承和模板.在熟悉了这些特性之后再研究这个例子也许会帮助.另一方面,这个例子还能很好地测试你对这些特性的理解程度. 前面示例的Sales_item和Query两个类的使用计数的实现是相同的.这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数.原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化.至于是公开还是隐藏下

C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还可以只特化push和pop成员.我们将特化push成员以复制字符数组,并且特化pop成员以释放该副本使用的内存: template<> void Queue<const char *>::push(const char *const &val) { char *new_item = new char[sizeof(val) + 1]; strncpy(new_item,val,sizeof(

C++ Primer 学习笔记_80_模板与泛型编程 --类模板成员

模板与泛型编程 --类模板成员 引言: 这一节我们介绍怎样实现前面提到的Queue模板类. 标准库将queue实现为其他容器之上的适配器.为了强调在使用低级数据结构中设计的编程要点,我们将Queue实现为链表.实际上,在我们的实现中使用标准库可能是个更好的决定!!-_-. 1.Queue的实现策略 如图所示,我们实现两个类: 1)QueueItem类表示Queue的链表中的节点,该类有两个数据成员item和next: a. item保存Queue中元素的值,它的类型随Queue的每个实例而变化:

C++ Primer 学习笔记_86_模板与泛型编程 --重载与函数模板

模板与泛型编程 --重载与函数模板 引言: 函数模板可以重载:可以定义有相同名字但参数数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数. 但是,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性. 一.函数匹配与函数模板 如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下: 1.为这个函数名建立候选函数集合,包括: a.与被调用函数名字相同的任意普通函数. b.任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配