Part 5. Templates and Generic Programming

本文介绍了C++中的泛型编程概念,包括函数模板和类模板的定义与使用,并详细解释了模板形参、非类型模板形参等关键知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

泛型编程与面向对象编程一样,都依赖于某种形式的多态性。面向对象编程中的多态性在运行时应用于存在继承关系的类。我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异。只要使用基类的引用或指针,基类类型或派生类类型的对象就可以使用相同的代码。

在泛型编程中,我们所编写的类和函数能够多态地用于跨越编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。标准库中的容器、迭代器和算法是很好的泛型编程的例子。

在 C++ 中,模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。


5.1 模板的定义和使用


5.1.1 定义函数模板

我们可以不用为每个类型定义一个新函数,而是只定义一个函数模板(function template)。函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。例如,可以编写名为 compare 的函数模板,它告诉编译器如何为我们想要比较的类型产生特定的 compare 版本。下面是 compare 的模板版本:

// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。  函数形参表定义了特定类型的局部变量但并不初始化那些变量,在运行时再提供实参来初始化形参。模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。类型形参跟在关键字 class 或 typename 之后定义,例如,class T 是名为 T 的类型形参,在这里 class 和 typename 没有区别。 

使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。例如:

     int main ()
     {
         // T is int;
         // compiler instantiates int compare(const int&, const int&)
         cout << compare(1, 0) << endl;
         // T is string;
         // compiler instantiates int compare(const string&, const string&)
         string s1 = "hi", s2 = "world";
         cout << compare(s1, s2) << endl;
         return 0;
     }
函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前。
// ok: inline specifier follows template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of inline specifier
inline template <typename T> T min(const T&, const T&);

5.1.2 类模板

我们这里以STL库中的queue类的实现为例来介绍类模板的使用,我们自定义的 Queue 类必须能够支持不同类型的对象,所以将它定义为类模板。

先定义这个类的接口:

template <class Type> class Queue {
    // needs access to head 
    friend std::ostream& 
    operator<< <Type> (std::ostream&, const Queue<Type>&);
public:
    // empty Queue
    Queue(): head(0), tail(0) { }
public:
    // construct a Queue from a pair of iterators on some sequence
    template <class It> 
    Queue(It beg, It end): 
          head(0), tail(0) { copy_elems(beg, end); }
    // . . .

    // copy control to manage pointers to QueueItems in the Queue
    Queue(const Queue &Q): head(0), tail(0) 
                                  { copy_elems(Q); }
    Queue& operator=(const Queue&);
    ~Queue() { destroy(); }

    // replace current Queue by contents delimited by a pair of iterators
    template <class Iter> void assign(Iter, Iter);
    // rest of Queue class as before

    // return element from head of Queue
    // unchecked operation: front on an empty Queue is undefined
    Type& front()             { return head->item; }
    const Type &front() const { return head->item; }

    void push(const Type &);      // add element to back of Queue
    void pop();                   // remove element from head of Queue

    bool empty() const {          // true if no elements in the Queue
        return head == 0;
    }
private:
    QueueItem<Type> *head;         // pointer to first element in Queue
    QueueItem<Type> *tail;         // pointer to last element in Queue

    // utility functions used by copy constructor, assignment, and destructor
    void destroy();                // delete all the elements
    void copy_elems(const Queue&); // copy elements from parameter
private:
    // version of copy to be used by assign to copy elements from iterator range
    template <class Iter> void copy_elems(Iter, Iter); 
};
除了模板形参表外,类模板的定义看起来与任意其他类问相似。类模板可以定义数据成员、函数成员和类型成员,也可以使用访问标号控制对成员的访问,还可以定义构造函数和析构函数等等。在类和类成员的定义中,可以使用模板形参作为类型或值的占位符,在使用类时再提供那些类型或值。

与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:

     Queue<int> qi;                 // Queue that holds ints
     Queue< vector<double> > qc;    // Queue that holds vectors of doubles
     Queue<string> qs;              // Queue that holds strings

5.1.3 模板形参

可以给模板形参赋予的唯一含义是区别形参是类型形参还是非类型形参。如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。
模板形参作用域的讨论
模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字:
     typedef double T;
     template <class T> T calc(const T &a, const T &b)
     {
          // tmp has the type of the template parameter T
          // not that of the global typedef
          T tmp = a;
          // ...
          return tmp;
     }
而且,用作模板的名字不能在模板的内部重用,而且形参的名字在模板形参表中只能使用一次。
对于模板而言,我们可以只声明而不定义。同一个模板的声明和定义中,模板形参的名字不必相同:
     // all three uses of calc refer to the same function template
     // forward declarations of the template
     template <class T> T calc(const T&, const T&) ;
     template <class U> U calc(const U&, const U&) ;
     // actual definition of the template
     template <class Type>
     Type calc(const Type& a, const Type& b) { /* ... */ }

5.1.4 类型模板形参

除了定义数据成员或函数成员之外,类还可以定义类型成员。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。例如,考虑下面的函数:
     template <class Parm, class U>
     Parm fcn(Parm* array, U value)
     {
         Parm::size_type * p; // If Parm::size_type is a type, then a declaration
                              // If Parm::size_type is an object, then multiplication
     }
我们知道 size_type 必定是绑定到 Parm 的那个类型的成员,但我们不知道 size_type 是一个类型成员的名字还是一个数据成员的名字,默认情况下,编译器假定这样的名字指定数据成员,而不是类型。
如果希望编译器将 size_type 当作类型,则必须显式告诉编译器这样做:
     template <class Parm, class U>
     Parm fcn(Parm* array, U value)
     {
         typename Parm::size_type * p; // ok: declares p to be a pointer
     }
通过在成员名前加上关键字 typename 作为前缀,可以告诉编译器将成员当作类型。通过编写 typename parm::size_type,指出绑定到 Parm 的类型的 size_type 成员是类型的名字。当然,这一声明给用实例化 fcn 的类型增加了一个职责:那些类型必须具有名为 size_type 的成员,而且该成员是一个类型。

5.1.4 非类型模板形参

模板形参不必都是类型。在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。
例如,下面的函数模板声明了 array_init 是一个含有一个类型模板形参和一个非类型模板形参的函数模板。函数本身接受一个形参,该形参是数组的引用:
     // initialize elements of an array to zero
     template <class T, size_t N> void array_init(T (&parm)[N])
     {
         for (size_t i = 0; i != N; ++i) {
             parm[i] = 0;
         }
     }
模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参(例如,像这里所做的一样)指定数组的长度。当调用 array_init 时,编译器从数组实参计算非类型形参的值:
     int x[42];
     double y[10];
     array_init(x);  // instantiates array_init(int(&)[42]
     array_init(y);  // instantiates array_init(double(&)[10]

5.1.5 模板代码的编写规范

编写模板代码时,对实参类型的要求尽可能少是很有益的。这个TIP虽然简单,但它说明了编写泛型代码的两个重要原则:
  1. 模板的形参是 const 引用(因为有些类型是不允许复制的,这种用法可以防止这种风险)。
  2. 函数体中的测试只用 < 比较。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值