函数模板(C++)

一、函数模板:

函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。

如果我们需要将两个相同类型的值对调,但是这两个值可能是int类型,可能是double类型甚至是其他类型的时候,我们首先会想到编写多个swap函数,但显然这样很复杂。

C++的函数模板功能能自动完成这一过程,可以节省时间,而且更可靠。
函数模板允许以任意类型的方式来定义函数。例如:

template<typename T>
void swap(T &a, T &b) {
    T temp;
    temp = a;
    a = b;
    b = temp;
}

第一行指出,要建立一个模板,并将类型命名为T。关键字templete和typename是必需的,除非可以使用关键字class代替typename。另外,必须使用尖括号。
模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int的函数时,编译器将按模板模式创建这样的函数,并用int代替T。
在标准C++98添加关键字typename之前,C++使用关键字class来创建模板。也就是说,可以这样编写模板定义:

template<class T>
void swap(T &a, T &b) {
    T temp;
    temp = a;
    a = b;
    b = temp;
}

二、重载模板

需要多个对不同类型使用同一种算法的函数时,可使用模板,然而,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载模板定义。
例如,交换两个数组的值的时候。

template<typename T>
void swap(T a[], T b[], int n) {
    T temp;
    for (int i = 0; i < n; i++) {
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

模板的局限性:

template<typename T>
void f(T a, T b) {
    ...
}

假设a、b为数组,那么我们就不能使用a=b,同理,若a、b为结构体,那么我们就不能直接比较a、b的大小(不重载运算符的情况下)。因此,编写的模板的函数很可能无法处理某些类型。
一种解决方案是:C++允许重载运算符,以便能够将其用于特定的结构或类。
另一种解决方案是:为特定类型提供具体化的模板定义。

显式具体化:

假设定义如下结构:

struct job {
    char name[40];
    double salary;
    int floor;
};

另外,假设希望能够交换两个这种结构的内容。由于C++允许将一个结构赋给另一个结构,因此即使T是一个job结构,上述代码也适用。然而,假设只想交换salary和floor成员,则需要使用不同的代码,但swap()的参数将保持不变(两个job结构的引用),因此无法使用模板重载来提供其他的代码。
因此,可以提供一个具体化函数定义——成为显示具体化(explicit specialization),其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。

具体化机制(C++标准定义的形式):第三代具体化

  • 对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版版本。
  • 显示具体化的原型和定义应以templete<>打头,并通过名称来指出类型。
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。

    下面是用于交换job结构的非模板函数、模板函数和具体化的原型:

//non template function prototype
void swap(job &, job &);

//template prototype
template<typename T>
void swap(T &, T &);

//explicit specialization for the job type
template <> void swap<job>(job &, job &);

正如前面指出的,如果有多个原型,则编译器在选择原型时,非模板版本优先于显式具体化和模板版本,而显式具体化优先于使用模板生成的版本。

而若我们只想交换salary和floor,我们只需用显示具体化方法即可。

template<>void swap<job>(job &j1, job &j2) {
    double t1;
    int t2;
    t1 = j1.salary;
    j1.salary = j2.salary;
    j2.salary = t1;
    t2 = j1.floor;
    j1.floor = j2.floor;
    j2.floor = t2;
}

实例化与具体化:

在代码中包含函数本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation),例如上面的swap(i,j)导致编译器生成swap()的一个实例,该实例使用int类型。模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化(implicit instantiation),因为编译器之所以知道需要进行定义,是由于程序调用swap()函数时提供了int参数。
C++允许显式实例化(explicit instantiation),这意味着可以直接命令编译器创建特定的实例,如

swap<int>()

其语法是,声明所需的种类——用<>符号指示类型,并在声明前加伤关键字template:

template void swap<int>(int, int);

实现了这种特性的编译器看到上述声明后,将使用swap()模板生成一个使用int类型的实例。也就是说,该声明的意思是“使用swap()模板生成int类型的函数定义。”

与显式实例化不同的是,显式具体化使用下面两个等价的声明之一:

template <> void swap<int>(int &, int &);
template <> void swap(int &, int &);

区别在于,这些声明的意思是“不要使用swap()模板来生成函数定义,而应使用专门为int类型显式地定义的函数定义”。

试图在同一个文件(或转换单元)中使用同一种类型的显式实例和显示具体化将出错

还可以通过在程序中使用函数来创建显示实例化。

template <typename T>
T add(T a, T b) {
    return a + b;
}
int main() {
    int m = 6;
    double x = 10.2;
    cout << add<double>(m, x) << endl;
    return 0;
}

这里的模板与函数调用不匹配,因为该模板要求两个函数参数的类型相同。但通过使用
add< double >(x,m),可强制为double类型实例化,并将参数m强制转换为double类型。

如果对swap()做类似的处理,例如:

int m = 5;
double x = 14.3;
swap<double>(m, x);

这将为类型double生成一个显式实例化,但这些代码不管用,因为第一个形参的类型为double &,不能指向int变量m。

隐式实例化、显式实例化和显式具体化统称为具体化(specialization)。它们的相同指出在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。

声明:以上整理自个人理解和Stephen Prata 著的《C++ Primer Plus》

猜你喜欢

转载自blog.csdn.net/MoooLi/article/details/82633821