C++ Templates

第四章 非类型模板参数

预计阅读时间4 分钟 13 views

对于函数模板和类模板,模板参数并不局限于类型,普通值也可以作为模板参数。

当要使用基于值(value)的模板时,就必须显式地指定这些值,才能够对模板进行实例化,并得到最终的代码。

4.1 非类型的类模板参数

现使用一个元素数目固定地数组来实现stack ,这种实现方法的优点是:不论是在亲自管理内存还是用标准容器来管理内存的情况下,都可以避免额外的内存开销。
然而,决定一个栈的大小是很麻烦的事情:

  • 如果指定的容器太小,那么栈可能溢出。
  • 如果指定的容器太大,那么可能会导致不必要的内存浪费。

一种解决方法就是:根据数据使用情况来指定栈的大小, 此时,可以把数组大小定义为一个模板参数:

#include <stdexcept>

template<typename T, int MAXSIZE>
class Stack{
public:
    Stack();
    void push(const T&);
    void pop();
    T top() const;
    bool empty() const { return numElems == 0; }
private:
    int numElems;
    T elems[MAXSIZE];
};

template<typename T, int MAXSIZE>
void Stack::Stack():numElems(0){ /* ... */ }

template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T& elem){
    if(numElems == MAXSIZE) throw std::out_of_range("Stack<>::push(): stack is full");
    elems[numElems++] = elem;
}

template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop() const{
    if(numElems <= 0) throw std::out_of_range("Stack::pop(): empty stack");
    --numElems;
}

template<typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top() const{
    if(numElems <= 0) throw std::out_of_range("Stack::top(): empty stack");
    return elems[numElems - 1];
}
C++

MAXSIZE是新加入的第二个模板参数,类型为int 。它指定了数组最多可包含的栈元素的个数:

template<typename T, int MAXSIZE>
class Stack{
    ...
private: T elems[MAXSIZE];
    ...
};
C++

push()中实现检查该栈是否已经满了:

template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(){
    if(numElems == MAXSIZE) throw std::out_of_range("Stack<>::push(): stack is full");
    elems[numElems++] = elem;
}
C++

在使用这个类模板之前,需要同时指定元素的类型和个数:

#include <stdexcept>
#include <iostream>

template<typename T, int MAXSIZE>
class Stack{
public:
    Stack();
    void pop();
    void push(const T&);
    T top() const;
    bool empty() const { return numElems == 0; }
private:
    int numElems;
    T elems[MAXSIZE];
};

template<typename T, int MAXSIZE>
Stack<T, MAXSIZE>::Stack():numElems(0){ }

template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T& elem){
    if(numElems == MAXSIZE) throw std::out_of_range("Stack<>::push(): stack is full");
    elems[numElems++] = elem;
}

template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop(){
    if(numElems == 0) throw std::out_of_range("Stack<>::pop(): empty stack");
    --numElems;
}

template<typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top() const{
    if(numElems == 0) throw std::out_of_range("Stack<>::top(): empty stack");
    return elems[numElems - 1];
}

int main(){
    try{
        Stack<int, 20> int20Stack;
        Stack<int, 40> int40Stack;
        Stack<std::string, 40> stringStack;

        int20Stack.push(7);
        std::cout << int20Stack.top() << '\n';
        int20Stack.pop();

        stringStack.push("hello");
        std::cout << stringStack.top() << '\n';
        stringStack.pop();
        stringStack.pop();
    }catch(const std::exception& ex){
        std::cerr << "Exception: " << ex.what() << '\n';
        return EXIT_FAILURE;
    }
}
C++

从上面的代码可以看出,每个实例模板都具有自己的类型,因此 int20Stack 和 int40Stack 属于不同的类型, 而且这两种类型之间也不存在显式或隐式的类型转换,所以它们之间不能相互替换,更不能相互赋值。

同样,也可以为模板参数指定缺省值:

template<
        typename T = int,
        int MAXSIZE = 100
        >
class Stack{
    ...
};
C++

针对上面的例子而言,从优化设计的角度来说,其并不适合使用缺省值, 因为对于一个栈的类型和大小而言,指定的缺省值不一定合适,因此最好亲自显式地指定这两个值。

4.2 非类型的函数模板参数

和类模板参数类似,也可以为函数模板定义非类型参数。
下面的函数模板定义了一组用于增加特定值的函数:

template<
         typename T,
         int VAL
        >
T addValue(const T& x){ return x + VAL; }
C++

将函数或者操作用作参数是非常有用的,这种操作在迭代器中经常使用,因为可以自定义迭代器的行为。

例如,借助标准模板库(STL),可以传递这个函数模板的实例给集合中的每一个元素,让其每个元素都增加一个整数值:

std::transform(source.begin(),      // appoint the beginning of target
               source.end(),        // appoint the end of target
               dest.begin(),        // appoint the beginning of receiver
               addValue<int, 5>);   // operation to each element
C++

在 std::transform() 中,最后一个实参实例化了函数模板 addValue() ,其作用是让类型为 int 的元素增加5, 源集合 source 中的每一个元素都会调用实例化后的 addValue() 函数,并把调用结果放入目标集合 dest

然而,实际情况是: addValue(5) 是一个函数模板实例,而函数模板实例通常被看成是用来命名一组重载函数的集合(即使该集合中只有一个函数), 在现有的 C++ 标准中,重载函数的集合并不能用于模板参数的演绎,因此,必须将这个函数模板的实参强制类型转换为具体的类型:

std::transform(source.begin(),
               source.end(),
               dest.begin(),
               (int(*)(const int&)) addValue<int, 5>);
C++

4.3 非类型模板参数的限制

非类型模板参数是有限制的,因为它们通常是常整数(包括枚举值)或者指向外部链接的指针。

浮点数和类对象(class-type)是不允许作为非类型模板参数的:

  • 浮点数不能作为非类型模板参数
template<double VAT>
double process(double v) { return v * VAT; }
C++
  • 类对象不能作为非类型模板参数
template<std::string name>
class MyClass{

};
C++

由于字符串文字是内部链接对象(因为两个具有相同名称但处于不同模块的字符串,是两个完全不同的对象),所以不能使用它们来作为模板实参:

template<const char* name>
class MyClass{

};
...
MyClass<"hello"> x;
/* error: valid argument or string here */
C++

此外,也不能使用全局指针作为模板参数:

template<const char* name>
class MyClass{

};
...
const char* s = "hello";
MyClass<s> x;
/* s is a pointer to the eternal object */
C++

但是,可以这样使用,链接一个外部对象:

template<const char* name>
class MyClass{

};
...
extern const char s[] = "hello"; // extern s
MyClass<s> x;
C++

4.4 小结

  • 模板可以具有值模板参数,而不仅仅是类型模板参数。
  • 对于非类型模板参数,不能使用浮点数、class类型的对象和内部链接对象(例如string)作为实参。

Leave a Comment

Share this Doc

第四章 非类型模板参数

Or copy link

CONTENTS
It's late! Remember to rest.