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

目录

 

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

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

二、模板的局限性

三、显式具体化

四、实例化和具体化~小结:

【2】继承中类模板的使用

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


【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】继承中类模板的使用

(1)继承中父子类和模板类的结合情况:

  1. 父类一般类,子类是模板类, 和普通继承的玩法类似;
  2. 子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数;
  3. 父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中;

  注: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么

#include <iostream>
using namespace std;
template <typename T>
class A
{
    public:
    A(T t)//函数的参数列表使用虚拟类型
    {
      this->t = t;
    }
    T &getT() //成员函数返回值使用虚拟类型
    {
      return t;
    }
private:
    T t; //成员变量使用虚拟类型
};

template <typename Tb>
//class B: public A<int>  后面只能B<int> b(888);这样使用
class B: public A<string>  //父类类型已被定死为 A<string> 
{
public:
    B(Tb b):A<Tb>(b)
    {
        this->b = b;
    }
private:
    Tb b;
};

void printA(A<int> &a)
{
    cout<<a.getT()<<endl;
}
int main(void)
{
    //1.模板类定义类对象,必须显示指定类型
    //2.模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
    A<int>  a(666);
    cout<<a.getT()<<endl;

    B<string> b("hello");
    cout<<"b(hello): "<<b.getT()<<endl;

    //模板类做为函数参数
    printA(a);

    return 0;
}

(2)类模板的继承,在类层次中的特点:

类模板在进行继承时必须明确类型

  1. 类模板可以从模板类派生;
  2. 模板类也可以从非模板类派生;
  3. 非模板类从模板类派生,模板类需要实例化,传递类型;
  4. 模板类可以包含虚函数;
1、模板类-继承-模板类
template<class T>
class C 
{
public:
    T x;
    C(T t) :x(t) {  }
    void print()
    {
        cout << x<<"\n";
    }
    virtual void run() //声明虚函数,优先调用派生类的成员函数(模板类可以包含虚函数)
    {
        cout << x << "\n";
    }         
    virtual void go() = 0; // 纯虚函数,模板抽象类
};
template<class T>
class D :public C<T>
{
public:
    T y;
    D(T t1, T t2) :C(t1), y(t2){}
    void print()
    {
        cout <<x<<' '<< y<<"\n";
    }
    void run()
    {
        cout << x << ' ' << y << "\n";
    }
    void go(){}
};
int main()//模板类包含虚函数
{
    C<int> *p = new D<int>(1,2);
    p->run();//调用派生类的成员函数

    D<string> d1("hello", "world");
    d1.print();
    
    return;
}
2、模板类-继承-普通类
class xyz
{
public:
    int x;
    int y;
    int z;

    xyz() { x = y = z = 0;}
    void print()
    {
        cout << x << ' ' << y << ' ' << z << endl;
    }
};
template<class T>
class E :public xyz
{
public:
    T a;
    E(T t1) :a(t1){}
    void print()
    {
        cout << "a=" <<a<< endl;
        cout << x << ' ' << y << ' ' << z << endl;
    }
};
3、普通类-继承-模板类(非模板类从模板类派生)
class F :public E<int> //传递类型
{
public:
    int ff=0;
    F(int a, int b) :E<int>(a), ff(b){}
    void print()
    {
        cout << ff << endl;
        cout << "a=" << a << endl;
        cout << x << ' ' << y << ' ' << z << endl;
    }
};
int main()
{
    F f(1,2);
    f.print();
    E<string> e1("hahahhaha");
    e1.print();

    return;
}

(参考:类模板与模板类的特点,类模板的继承方式以及的继承特点)

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

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

template <typename T>
struct E
{
}

template DDD : E<int>
{

}

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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值