【C++】一文理清C++模板的具体化(隐式实例化,显式实例化,显式具体化,部分具体化)


在学习C++的模板时,经常遇到这四个概念:隐式实例化,显式实例化,显式具体化,部分具体化,但是这四个概念到底在讲些什么东西呢?他们在什么场景下会被使用?又如何区分呢?可能很多小伙伴不能立即分辨出来,所以,接下来让我们理清这四个概念之间的差别:

一、关于模板

首先,我们先明确一个概念:类模板和函数模板并不是具体的类和成员函数,它们是C++编译器指令,是类的蓝图,它说明了如何生成类和成员函数定义。

根据这个概念,我们不难推导出,只有我们在使用到具体的类或者函数时,这个类或者函数才会被按照之前定义好的“蓝图”生成出来。(最近在玩塞尔达,所以造高达的时候蓝图这个概念对我来说很好理解hhh。)所以,我们不用的时候,这个类就没有被生成出来。

有了这个概念,会帮助我们更好的理解接下来的这些问题。

二、关于具体化

2.1 隐式实例化(用的时候再准备)

声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义。编译器在需要对象之前,不会生成类的隐式实例化:

ArrayTP<int, 100> stuff;		// 隐式实例化

ArrayTP<double, 30> * pt;		// 此时还没有使用创建对象,没有分配内存,只是创建了一个指针指向这样的对象
pt = new ArrayTP<double, 30>;	// 该语句导致编译器生成类定义,并根据该定义创建一个对象。

这是我们最常用的一种实例化的方式。

2.2 显式实例化(没用的时候就准备)

显式实例化与前面的隐式实例化不同的是:实例化的时机不同。

前面说我们写好了一个模板类,但实际上它只是一个编译指令,只有当我们用的时候,才会根据模板类去生成具体的类,而什么时候用取决于使用者。这也就是前面提到的隐式实例化。而此处的显式实例化指的是,在我们写好一个模板类后,如果我们能确定某个类在接下来的业务逻辑中一定会被用到,则我们可以在这时候抢先实例化出这个类。这样就比隐式实例化时更快速,从而省去运行阶段创建这个类的时间。

当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中。虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。

//假如我们前面已经定义好了ArrayTP这个模板类,显式实例化就是我判断接下来ArrayTP<string,100>这个类一定会被使用到,因此先下手为强,抢先一步对这个类进行实例化。
template class ArrayTP<string, 100>; _// generate ArrayTP<string, 100> class_

2.3 显式具体化(全部特殊处理)

有没有想过C++为什么要支持模板类呢?

很显然,在我们写业务逻辑的时候,可能会遇到很多相似而不相同的地方。比如,有时候我们可能需要一个数据类型为int的队列,好的,我们定义一个。写着写着发现我们又需要一个数据类型为double的队列,但是这个队列和前面的int队列的所有功能一模一样,你这时候可能会想:那我把前面的代码全都复制粘贴过来,然后把int改为我们现在所需要的double,大功告成!然后,接下来的过程中我们重复的遇到刚刚的问题,我们需要char,float,string…类型的队列,好吧,按你的方法,我们把之前定义好的全都复制粘贴过来,然后改成我们需要的数据类型…Fine! 终于把所有的类都定义好了。这时候回过头看一眼你的代码:太冗长了!

基于这种类似情况,C++提供了模板的概念,我们可以把这些相似但不相同的类和函数通过模板解决了!但是这时候新的情况又出现了,有时候,尽管你的模板类支持int,double,float,char…等等数据类型,但是等到下文去用的时候发现,虽然这个模板类可以处理百分之九十的东西,但是总是存在一些例外是前文定义的模板类所不能支持的,诚然,这个时候我们就要特殊情况特殊处理了。

这种特殊情况特殊处理的方式就是C++里的具体化。

我们先来了解一下显式具体化。

假设已经有了类模板的定义,需要对其中的一些进行修改,重新进行类的实现。有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。在这种情况下,可以提供一个显式模板具体化。当具体化模板和通用模板都与实例化请求匹配时,编译器将优先使用具体化版本。

举例:

假设已经为用于表示排序后数组的类(元素在加入时被排序)定义了一个模板:假设已经为用于表示排序后数组的类(元素在加入时被排序)定义了一个模板:

template<typename T>
class SortedArray
{   
}

假设模板使用>运算符来对值进行比较。对于数字,这管用;如果T表示一种类,则只要定义了T::operator>( )方法,这也管用;但如果T是由const char *表示的字符串,这将不管用。

在这种时候,可以提供一个显式模板具体化,这将采用为具体类型定义的模板,而不是为泛型定义的模板。

template<> class SortedArray<const char *>
{
}

其中的实现代码将使用strcmp( )(而不是>)来比较数组值。
现在,当请求const char *类型的SortedArray模板时,编译器将使用上述专用的定义,而不是通用的模板定义。也就是说,假设你考虑的很周全,为你的程序定义了通用的模板和一些需要特殊处理情况下的模板,那么在实例化的时候,编译器如果遇到这种特殊情况,就会特殊处理。

SortedArray<int> scores;
SortedArray<const char*> dates;

2.4 部分具体化(部分特殊处理)

和前面的显式具体化一样,部分具体化也是具体化的一种方式。处理的场景也跟前面差不多。那么为什么叫部分具体化呢?

前面的显式具体化里,我们的模板参数只有一个,如果我们对其进行具体化,就相当于对全部参数进行具体化;但是也有一些情况,我们会遇到需要两个参数的模板:

template<class T1,class T2> class Pair{...}

而我们的使用场景可能仅仅需要我们对第二个参数进行特殊处理,这时候我们就可以通过部分具体化,部分限制模板的通用性,仅对部分数据进行特殊处理:

//general template
template<class T1,class T2> class Pair{...}
//具体化
template<class T1> class Pair<T1,int>{...}//如果这里指定所有类型,<>就为空,就成为了显式具体化。
template<> class Pair<int,int>{...}

//如果有多个模板可供选择,编译器将使用具体化程度最高的模板。给定上述三个模板,情况如下:
Pair<double,double> p1;//通用
Pair<double,int> p2;//部分具体化
Pair<int,int>p3;//显式具体化

关键字template后面的<>声明的是没有被具体化的类型参数。

部分具体化特性使得能够设置各种限制。例如:

//general template
template<class T1,class T2,class T3> class Trio{...};
//specialization with T3 set to T2
template<class T1,class T2> class Trio<T1,T2,T2>{...};
//specialization with T3 and T2 set to T1*
template<class T1> class Trio<T1,T1*,T1*>{...};

//给出上述声明,编译器将作出以下选择:
Trio<int,short,char*> t1;//use general template
Trio<int,short> t2;//use Trio<T1,T2,T2>
Trio<char,char*,char*> t3;//use Trio<T1,T1*.T1*>

三、示例

这里给出一段示例代码,简单模拟以上使用场景:

#include <iostream>
using namespace std;

template<class T1, class T2>
class A
{
public:
    void show();
};

template<class T1, class T2>
void A<T1, T2>::show()
{
    cout << "this is a general definition." << endl;
}

// 显式实例化,A<double, double>这个类在内存中已经存在
template class A<double, double>;

// 显示具体化,不再使用通用的方法,所有类型都指定为某一特定的类型
template<>
class A<int, int>
{
public:
    void show();
};
// template<>   不用这一行语句
void A<int, int>::show()
{
    cout << "explicit specialization." << endl;
}

// 部分具体化
template<class T1>  // <>中放没有具体化的,T1泛型,T2指定为某种特殊的类型
class A<T1, int>
{
public:
    void show();
};
template<class T1>  // 注意这里
void A<T1, int>::show()
{
    cout << "partial specialization." << endl;
}

int main(void)
{
    A<char, char> a1;   // 隐式实例化
    a1.show();
    A<int, int> a2;     // 显示具体化过
    a2.show();
    A<double, int> a3;  // 调用部分具体化
    a3.show();

    return 0;
}

四、总结

对于隐式实例化和显式实例化:
区别仅仅是根据模板创建具体的类的时机不同。隐式实例化是当我们用到的时候才会创建具体的类和对象;显式实例化是在写代码时我们通过合理预判,断定某个特定的类必定会被用到,然后显式的实例化出具体的类,这样在节省了之后的创建时间。

对于显式具体化和部分具体化:
显然,模板是为了通用。而实际使用的过程中,只写一个模板很难做到对所有的类都通用,有一些可能需要特殊处理,这时候C++提供了具体化去限制模板的通用性。显式具体化是对所有参数进行限制,部分具体化是对部分的参数进行限制。从而在以后遇到这些情况时,编译器便能够特殊情况特殊处理。

当具体化模板和通用模板都与实例化请求匹配时,编译器将优先使用具体化版本。
当显式具体化和部分具体化模板都与实例化请求匹配时,编译器会优先使用具体化程度更高的版本。

以上,便是关于C++模板特性中如何辨别和使用隐式实例化,显式实例化,显式具体化,部分具体化的内容,希望能够帮助你理解这些特性。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值