C++模板编程


614 浏览 5 years, 4 months

2.4 模板参数

版权声明: 转载请注明出处 http://www.codingsoho.com/

模板参数

在Grid示例中,Grid模板包含一个模板参数:保存在网格中元素的类型。编写这个类模板的时候,在尖括号中指定参数列表,如下所示:

template <typename T>

这个参数列表类似于函数或方法中的参数列表。和函数或方法一样,可以编写任意多模板参数的类。此外,这些参数未必是类型,而且可以有默认值。

1、非类型的模板参数

非类型的参数是“普通”参数,例如int和指针:即在函数和方法中熟悉的那种参数。然而,非类型的模板参数只能是整数类型(char、int、long...)、枚举类型、指针和引用。

在Grid模板类中,可通过非类型模板参数指定网格的高度和宽度,而不是在构造函数中指定这些。在模板参数中指定非类型参数而不是在构造函数中指定的主要好处是:在编译代码之前就知道这些参数的值了。

回顾前面提到的,编译器为模板化的方法生成代码的方式是在编译之前替换模板参数。因此,在这个实现中,可使用普通的二维数组而不是动态分配这个数组。下面是新的类定义:

template <typename T, size_t WIDTH, size_t HEIGHT>
class Grid
{
public:
    void setElementAt(size_t x, size_t y, const T& inElem);
    T& getElementAt(size_t x, size_t y);
    const T& getElementAt(size_t x, size_t y) const;
    size_t getHeight() const { return HEIGHT; }
    size_t getWidth() const { return WIDTH; }

protected:
    T mCells[WIDTH][HEIGHT];
};

这个类比之前的版本简单得多。注意,模板参数列表需要3个参数,网格中保存的对象类型以及网格的宽度和高度。宽度和高度用于创建保存对象的二维数组。在这个类中没有动态分配的内存,因此再也不需要用户定义的复制构造函数、析构函数和赋值运算符。事实上,甚至都不需要编写一个默认构造函数,编译器生成的那个就足够好了。下面是类方法的定义:

template <typename T, size_t WIDTH, size_t HEIGHT>
void Grid<T, WIDTH, HEIGHT>::setElementAt(size_t x, size_t y, const T& inElem)
{
    mCells[x][y] = inElem;
}

template <typename T, size_t WIDTH, size_t HEIGHT>
T& Grid<T, WIDTH, HEIGHT>::getElementAt(size_t x, size_t y)
{
    return mCells[x][y];
}

template <typename T, size_t WIDTH, size_t HEIGHT>
const T& Grid<T, WIDTH, HEIGHT>::getElementAt(size_t x, size_t y) const
{
    return mCells[x][y];
}

注意: 之前所有指定Grid<T>的地方,现在都必须指定Grid<T, WIDTH, HEIGHT>来表示这3个模板参数。

可通过以下方式实例化这个模板:

#include "Grid.h"

#include <iostream>
using namespace std;

int main()
{
    Grid<int, 10, 10> myGrid;
    Grid<int, 10, 10> anotherGrid;
    //
    myGrid.setElementAt(2, 3, 45);
    anotherGrid = myGrid;
    //
    cout << anotherGrid.getElementAt(2, 3);
    //
    return 0;
}

这段代码看上去很棒。除了声明一个Grid的语法稍微麻烦一些之外,实际的Grid代码简单得多。遗憾的是,实际中的限制比想象中的要多。首先,不能通过非常量的整数指定高度或宽度。下面的代码无法成功编译:

size_t height = 10;
Grid<int, 10, height> testGrid; //DOES NOT COMPILE

然而,如果把height声明为const,这段代码就可以编译了:

const size_t height = 10;
Grid<int, 10, height> testGrid; //DOES NOT COMPILE

第二个限制可能更明显。既然宽度和高度都是模板参数了,那么它们也是每一个网格类型的一部分了。这意味着Grid<int, 10, 10>和Grid<int, 10, 11 >是两种不同的类型。不能将一种类型的对象赋值给另外一种类型的对象,而且一种类型的变量不能传递给接受另一种类型的变量的函数或方法。

非类型参数是实例化的对象的类型规范中的一部分。

2.非类型参数的默认值

如果继续采用将高度和宽度作为模板参数的方式,那么您可能需要能够为这个高度和宽度提供默认值,就像之前Grid类的构造函数一样。C++允许使用类似的语法向模板参数提供默认值。

下面是类定义:

template <typename T, size_t WIDTH = 10, size_t HEIGHT = 10>
class Grid
{
}

不需要在方法定义的模板规范中指定WIDTH和HEIGHT的默认值。例如,下面是setElementAt()的实现:

template <typename T, size_t WIDTH, size_t HEIGHT>
void Grid<T, WIDTH, HEIGHT>::setElementAt(size_t x, size_t y, const T& inElem)
{
  mCells[x][y] = inElem;
}

现在,可以只通过一个元素类型实例化一个Grid,可以通过元素类型和宽度实例化,还可以通过元素类型、宽度和高度实例化:

int main()
{
    Grid<int> myGrid;
    Grid<int, 10> anotherGrid;
    Grid<int, 10, 10> aThirdGrid;
    return 0;
}

模板参数列表中默认参数的规则和函数及方法的规则是一样的。可以从右向左提供参数的默认值。