欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++程序员应了解的那些事(95)C++模板使用集锦

程序员文章站 2022-07-12 22:48:07
...

【1】C++函数模板的隐式实例化、显式实例化与显式具体化:统称具体化

一、什么是实例化和具体化?

        为进一步了解模板,必须理解术语实例化和具体化。

(1)实例化:在程序中的函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例。这即是函数模板的实例化!

        而函数模板实例化又分为两种类型:隐式实例化和显式实例化。

<示例>
template < typename T >
void Swap( T &a, T &b )
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}
int main(void)
{
    int a= 1, b = 2;
    Swap(a, b);
    Swap<int>(a, b);
    return 0;
}

        可以发现,在主函数中有两种Swap函数调用:

        第一个Swap(a, b)导致编译器自动识别参数类型生成一个实例,该实例使用int类型,此为隐式实例化。

        而第二个Swap<int>(a, b),直接命令编译器创建特定的int类型的函数实例,用<>符号指示类型,此为显式实例化。

(2)具体化:即显式具体化,与实例化不同的是,它也是一个模板定义,但它是对特定类型的模板定义。显式具体化使用下面两个等价的声明之一:

template <> void Swap<int>(int &, int &);
template <> void Swap(int &, int &);//Swap<job>中的<job>是可选的,因为函数的参数类型表明,这是job的一个具体化。

        可以发现,显式具体化声明在关键字template后包含<>。上面声明的意思是"不要使用Swap()模板来生成函数定义,而应使用专门为int类型显式地定义的函数的定义"。这些原型必须有自己的函数定义。

       在这里,有人可能要说了:”明明可以通过隐式实例化自动生成int类型的函数定义,为何还要弄出一个显式具体化来弄出另外一个模板呢?这不是多此一举吗?”我要解释一下,显式具体化的主要用途!而在介绍用途之前,我们先来了解一下普通函数模板的局限性。

二、模板的局限性

        假设有如下模板函数:

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

        通常代码假定可执行哪些操作。例如下面的代码假定定义了赋值,但是如果T为数组,这种假设将不成立!

a = b;

同样,下面的语句假设定义了<,但如果T为结构,则该假设便不成立!

if ( a > b )

        另外,为数组名定义了运算符 > ,但由于数组名是常量地址,因此它比较的是数组的地址,而这并不是我们所期望的操作。下面的语句假定为类型T定义了乘法运算符,但如果T为数组、指针或结构,这种假设便不成立:

T c = a * b;

        总之,编写的模板函数很可能无法处理某些类型。通常在C++中有一种解决方案是:运算符重载,以便能将其用于特定的结构或类。就是说一个类重载了运算符+之后,使用运算符+的模板便可以处理重载了运算符+的结构。

       但是,还有另外一种解决方案:为特定类型提供具体化的模板定义(这就是显式具体化的主要用途)。

三、显式具体化

假定定义了如下结构:

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

        另外,假设希望能够交换两个这种结构的内容。原来的模板使用下面的代码来完成交换:

temp  =  a;
a = b;
b = temp;

        由于C++允许将一个结构赋给另一个结构,因此即使T是一个job结构,上述代码也可适用。然而,如果只想交换salary和floor成员,而不交换name成员,则需要使用不同的处理代码。但Swap() 的参数将保持不变(两个job结构的引用),因此无法使用模板的重载来提供其他代码(模板重载,模板的参数列表必须不同)。这时可以提供一个具体化函数定义——称为显式具体化(explicit specialization),来实现这个需求。当编译器找到与函数调用匹配的具体化定义时,将使用该定义而不再寻找模板。

具体化机制随着C++的演变而不断变化。下面介绍C++标准定义的形式。
1.第三代具体化(ISO/ANSI C++标准)
试验其他具体化方法后,C++98标准选择了下面的方法。
    ①对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本。
    ②显式具体化的原型和定义应以template<>开头,并通过名称来指出类型。
    ③具体化优先于常规模板,而非模板函数优先于具体化和常规模板。

       上面已经介绍过显式具体化的声明方式,我们直接通过代码实例来看一下:

#include <iostream>
using namespace std;
//job结构
struct job
{
    char name[40];
    double salary;
    int floor;
};
//普通交换模板
template <typename T>
void Swap(T &a, T &b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}
//job类型的显式具体化模板
template <> void Swap<job>(job &j1, job &j2)
{
    double t1;
    int t2;

    //交换salary
    t1 = j1.salary;
    j1.salary = j2.salary;
    j2.salary = t1;

    //交换floor
    t2 = j1.floor;
    j1.floor = j2.floor;
    j2.floor = t2;
}
 
int main(void)
{
    int inta = 1, intb = 2;
    job zhangSan = {"张三", 80000, 6};
    job liSi = {"李四", 60000, 4};

    cout << "inta = " << inta << " inta = " << intb << endl;
    cout << zhangSan.name << " , " << zhangSan.salary << " , " << zhangSan.floor <<endl; 
    cout << liSi.name << " , " << liSi.salary << " , " << liSi.floor <<endl; 

    Swap(inta, intb); //编译器将实例化普通模板的int类型函数
    Swap(zhangSan, liSi);  //编译器将实例化显式具体化模板job类型函数

    cout << "\n交换后:\n" << endl;
    cout << "inta = " << inta << " inta = " << intb << endl;
    cout << zhangSan.name << " , " << zhangSan.salary << " , " << zhangSan.floor <<endl; 
    cout << liSi.name << " , " << liSi.salary << " , " << liSi.floor <<endl;
    return 0;
}

        在程序运行时匹配模板时,遵循的优先级是:具体化模板优先于常规模板,而非模板函数优先于具体化和常规模板!

实例化和具体化~小结:

       为进一步了解模板,必须理解术语实例化和具体化。记住,在代码中函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation)。例如,上面代码中,函数调用Swap(inta, intb))导致编译器生成Swap()的一个实例,该实例使用int类型。模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化(implict instantiation),因为编译器之所以知道需要进行定义,是由于程序调用Swap()函数时提供了int参数。

        最初,编译器只能通过隐式实例化来使用模板生成函数定义。但现在C++还允许显式实例化(explici instantiation)。这意味着可以直接命令编译器创建特定的实例,如Swap<int>()。其语法是:声明所需的种类——用<>符号指示类型,并在声明前加上关键字template:

        template void Swap<int>(int , int);//explicit instantiation

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

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

template <> void Swap<int>(int &, int &);//explicit specialization
template <> void Swap(int &, int &);//explicit specialization

        区别在于这些声明的意思是”不要使用Swap()模板来生成函数定义,而应使用专门为int类型显式地定义的函数定义。“这些原型必须有自己的函数定义。显式具体化声明在关键字template后包含<>,而显式实例化没有!

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

        还可通过在程序中使用函数来创建显式实例化。例如, 请看下面代码:

#include<bits/stdc++.h>
using namespace std;
template <class T>
T Add(T a, T b)
{
    return a+b;
}
int main()
{
    int m = 6;
    double x = 10.2;
    cout << Add<double>(x, m) << endl;//显式实例化
    return 0;
}

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

如果对Swap()做类似的处理,结果将如何呢?

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

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

【2】C++ 模板何时被实例化

A:请问c++模板被继承时会发生实例化吗
答:只谈继承肯定不会实例化,实例化发生在调用点,不掉用不会实例化,如果你定义了一个派生类对象,那被继承的模板基类也就要实例化一份了
A:这种被继承的父类已经指名具体类型的,父类也不会发生实例化吗?

template <typename T>
struct E
{
}

template DDD : E<int>
{

}

答:新城 不会的,类型又不占空间,实例化它干啥,除非被调用,如一个函数模板,或者要分配内存空间,比如定义一个占内存的对象
 有类型, 没变量! 一不涉及调用,二不涉及分配内存,编译器不会主动去实例化

 

 

相关标签: 程序员应知应会