首页 > 代码库 > template(2.2)

template(2.2)

3.3 Class Templates 的特化( Specializations)

你可以针对某些特殊的 template arguments,对一个 class template 进行「特化」。class templates的特化与 function template 的重载类似,使你得以针对某些特定类型进行程序代码优化, 或修正某个特定类型在 class template 实例(instantiation)中的错误行为。然而如果你对一个 class template 进行特化,就必须特化其所有成员函数。虽然你可以特化某个单独的成员函数,但一旦这么做,也就不再是特化整个 class template。

欲特化某个class template,必须以template<> 开头声明此一class,后面跟着你希望的特化结果。特化类型(specialized type)将作为template arguments 并在class名称之后直接写明:

template<>
class Stack<std::string> {
...
};

对特化体(specializations)而言,每个成员函数都必须像常规的(一般的)成员函数那样定义,每一个 T 出现处都必须更换为特化类型(specialized type):

void Stack<std::string>::push (std::string const& elem)
{
elems.push_back(elem); // 将传入的 elem 附加于尾
}

下面是一个针对 std::string 类型而特化的 Stack<> 的完整范例:

stack2.hpp

/* The following code example is taken from the book
 * "C++ Templates - The Complete Guide"
 * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
 *
 * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
 * Permission to copy, use, modify, sell and distribute this software
 * is granted provided this copyright notice appears in all copies.
 * This software is provided "as is" without express or implied
 * warranty, and with no claim as to its suitability for any purpose.
 */
#include <deque>
#include <string>
#include <stdexcept>
#include "stack1.hpp"

template<>
class Stack<std::string> {
  private:
    std::deque<std::string> elems;  // elements

  public:
    void push(std::string const&);  // push element
    void pop();                     // pop element
    std::string top() const;        // return top element
    bool empty() const {            // return whether the stack is empty
        return elems.empty();
    }
};

void Stack<std::string>::push (std::string const& elem)
{
    elems.push_back(elem);    // append copy of passed elem
}

void Stack<std::string>::pop ()
{
    if (elems.empty()) {
        throw std::out_of_range
                ("Stack<std::string>::pop(): empty stack");
    }
    elems.pop_back();         // remove last element
}

std::string Stack<std::string>::top () const
{
    if (elems.empty()) {
        throw std::out_of_range
                ("Stack<std::string>::top(): empty stack");
    }
    return elems.back();      // return copy of last element
}

 

此例之中,我们在stack内部改用deque代替vector来管理元素。这么做并没有特别的好处,但它示范「一个特化实作码可以和其primary template(主模板。译注:最原始的那一份定义)有相当程度的差异」。

3.4 偏特化( Partial Specialization)

Class templates 可以被偏特化(partial specialized,或称部份特化、局部特化)。这使你得以在特定情形下使用特殊实作码,但仍然留给你(使用者)选择 template parameters 的能力。例如对于下面的 class template:

template <typename T1, typename T2>
class MyClass {
...
};

以下数种形式的偏特化都是合理的:

// 偏特化:两个 template parameter 相同
template <typename T>
class MyClass<T,T> {
...
};
// 偏特化:第二个类型为int
template <typename T> 
class MyClass<T,int> {
... };
// 偏特化:两个 template parameter 均为指针类型
template <typename T1, typename T2>
class MyClass<T1*, T2*> {
...
};

以下例子示范,下列各种声明语句将使用上述哪一个 class template:

MyClass<int,float> mif; // 使用 MyClass<T1,T2>

MyClass<float,float> mff; // 使用 MyClass<T,T>

MyClass<float,int> mfi; // 使用 MyClass<T,int>

MyClass<int*,float*> mp; // 使用

MyClass<T1*,T2*>

如果某个声明语句与两个(或更多)偏特化版本产生同等的匹配程度,这个声明语句便被视为模棱两可(歧义):

MyClass<int,int> m; // 错误:同时匹配 MyClass<T,T> 和 MyClass<T,int>

MyClass<int*,int*> m; // 错误:同时匹配 MyClass<T,T> 和 MyClass<T1*,T2*>

为解除上述第二声明的歧义性,你可以针对「指向相同类型」的指针,提供另一个偏特化版本:

template <typename T>
class MyClass<T*,T*> {
...
};

3.5 预设模板自变量( Default Template Arguments)

你可以针对 class templates 定义其 template parameters 的默认值,这称为 default template arguments(预设模板自变量)。预设自变量值甚至可以引用前一步声明的 template parameters。例如在 class Stack<> 中,你可以把「用来容纳元素」的容器类型定义为第二个template parameter,

并使用std::vector<> 作为其默认值:

stack3.hpp

/* The following code example is taken from the book
 * "C++ Templates - The Complete Guide"
 * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
 *
 * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
 * Permission to copy, use, modify, sell and distribute this software
 * is granted provided this copyright notice appears in all copies.
 * This software is provided "as is" without express or implied
 * warranty, and with no claim as to its suitability for any purpose.
 */
#include <vector>
#include <stdexcept>

template <typename T, typename CONT = std::vector<T> >
class Stack {
  private:
    CONT elems;               // elements

  public:
    void push(T const&);      // push element
    void pop();               // pop element
    T top() const;            // return top element
    bool empty() const {      // return whether the stack is empty
        return elems.empty();
    }
};

template <typename T, typename CONT>
void Stack<T,CONT>::push (T const& elem)
{
    elems.push_back(elem);    // append copy of passed elem
}

template <typename T, typename CONT>
void Stack<T,CONT>::pop ()
{
    if (elems.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elems.pop_back();         // remove last element
}

template <typename T, typename CONT>
T Stack<T,CONT>::top () const
{
    if (elems.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elems.back();      // return copy of last element
}

注意上述的 template 如今有两个参数,所以每一个成员函数的定义式中都必须包含这两个参数:

template <typename T, typename CONT>
void Stack<T,CONT>::push (T const& elem)
{
elems.push_back(elem); // 追加元素
}

你可以像面对「单一template parameter」版本那样地使用这个新版stack。这时如果你只传递一个自变量表示元素类型,stack会使用预设的vector来管理其内部元素:

template <typename T, typename CONT = std::vector<T> >
class Stack {
private:
CONT elems; // 元素
...
};

当你在程序中声明一个Stack object时,也可以明确指定元素容器的类型:

/* The following code example is taken from the book
 * "C++ Templates - The Complete Guide"
 * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
 *
 * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
 * Permission to copy, use, modify, sell and distribute this software
 * is granted provided this copyright notice appears in all copies.
 * This software is provided "as is" without express or implied
 * warranty, and with no claim as to its suitability for any purpose.
 */
#include <iostream>
#include <deque>
#include <cstdlib>
#include "stack3.hpp"

int main()
{
    try {
        // stack of ints:
        Stack<int> intStack;

        // stack of doubles which uses a std::deque<> to manage the elements
        Stack<double,std::deque<double> > dblStack;

        // manipulate int stack
        intStack.push(7);
        std::cout << intStack.top() << std::endl;
        intStack.pop();

        // manipulate double stack
        dblStack.push(42.42);
        std::cout << dblStack.top() << std::endl; 
        dblStack.pop();
        dblStack.pop();
    }
    catch (std::exception const& ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
        return EXIT_FAILURE;  // exit program with ERROR status
    }
}

那么,只要像下面这样做:

Stack<double,std::deque<double> >

你就声明了一个 double stack,其内部以 std::deque<> 来管理元素。

3.6 摘要

  所谓 class template 是「包含一个或多个尚未确定之类型」的 class。

  你必须将具体类型当作 template arguments 传入,才能使用 class template。于是该 class template便以你所指定的那些类型,由编译器加以实例化并编译。

  Class templates 之中,只有实际被调用的成员函数,才会被实例化。

    你可以针对某些特定类型,对 class templates 进行特化(specialize)。

  你可以针对某些特定类型,对 class templates 进行偏特化(partially specialize)。 

    你可以为 template parameters 定义默认值(称为 default template arguments),该预设自变量值 可以引用前一步定义的 template parameters。

 

 

/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */#include <vector>#include <stdexcept>
template <typename T, typename CONT = std::vector<T> >class Stack {  private:    CONT elems;               // elements
  public:    void push(T const&);      // push element    void pop();               // pop element    T top() const;            // return top element    bool empty() const {      // return whether the stack is empty        return elems.empty();    }};
template <typename T, typename CONT>void Stack<T,CONT>::push (T const& elem){    elems.push_back(elem);    // append copy of passed elem}
template <typename T, typename CONT>void Stack<T,CONT>::pop (){    if (elems.empty()) {        throw std::out_of_range("Stack<>::pop(): empty stack");    }    elems.pop_back();         // remove last element}
template <typename T, typename CONT>T Stack<T,CONT>::top () const{    if (elems.empty()) {        throw std::out_of_range("Stack<>::top(): empty stack");    }    return elems.back();      // return copy of last element}