目录
【1】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)继承中父子类和模板类的结合情况:
- 父类一般类,子类是模板类, 和普通继承的玩法类似;
- 子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数;
- 父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中;
注: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么
#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、模板类-继承-模板类
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>
{
}
答:新城 不会的,类型又不占空间,实例化它干啥,除非被调用,如一个函数模板,或者要分配内存空间,比如定义一个占内存的对象
有类型, 没变量! 一不涉及调用,二不涉及分配内存,编译器不会主动去实例化