C++模板之隐式实例化、显示实例化、隐式调用、显示调用和模板特化详解

代码编译运行环境:VS2012+Debug+Win32



模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程。对于函数模板而言,模板实例化之后,会生成一个真正的函数。而类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始化。模板的实例化分为隐式实例化和显示实例化。

对函数模板的使用而言,分为两种调用方式,一种是显示模板实参调用(显示调用),一种是隐式模板实参调用(隐式调用)。对于类模板的使用而言,没有隐式模板实参和显式模板实参使用的说法,因为类模板的使用必须显示指明模板实参。各个概念请勿混淆。

1.隐式实例化

1.1模板隐式实例化的定义

这是相对于模板显示实例化而言。在使用模板函数和模板类时,不存在指定类型的模板函数和模板类的实体时,由编译器根据指定类型参数隐式生成模板函数或者模板类的实体称之为模板的隐式实例化。

1.2函数模板隐式实例化

函数模板隐式实例化指的是在发生函数调用的时候,如果没有发现相匹配的函数存在,编译器就会寻找同名函数模板,如果可以成功进行参数类型推演,就对函数模板进行实例化。

还有一种简介调用函数的情况,也可以完成函数模板的实例化。所谓的简介调用是指将函数入口地址传给一个函数指针,通过函数指针完成函数调用。如果传递给函数指针不是一个真正的函数,那么编译器就会寻找同名的函数模板进行参数推演,进而完成函数模板的实例化。参考如下示例。

#include <iostream>
using namespace std;
template <typename T> void func(T t){
    cout<<t<<endl;
}

void invoke(void (*p)(int)){
    int num=10;
    p(num);
}
int main(){
    invoke(func);
}

程序成功运行并输出10。

1.3类模板隐式实例化

类模板隐式实例化指的是在使用模板类时才将模板实例化,相对于类模板显示实例化而言的。考察如下程序。

#include <iostream>
using namespace std;
template<typename T>class A{
    T num;
public:
    A(){
        num=T(6.6);
    }
    void print(){
        cout<<"A‘num:"<<num<<endl;
    }
};

int main(){
    A<int> a; //显示模板实参的隐式实例化
    a.print();
}

程序输出结果:A’num:6。


2. 显示实例化

2.1模板显示实例化的定义

显示实例化也称为外部实例化。在不发生函数调用的时候讲函数模板实例化,或者在不适用类模板的时候将类模板实例化称之为模板显示实例化。

2.2函数模板的显示实例化

对于函数模板而言,不管是否发生函数调用,都可以通过显示实例化声明将函数模板实例化,格式为:

template [函数返回类型] [函数模板名]<实际类型列表>(函数参数列表)

例如:

template void func<int>(const int&);

2.3类模板的显示实例化

对于类模板而言,不管是否生成一个模板类的对象,都可以直接通过显示实例化声明将类模板实例化,格式为:

template class [类模板名]<实际类型列表>

例如:

template class theclass<int>;

3.函数模板调用方式

3.1隐式模板实参调用

在发生函数模板的调用时,不显示给出模板参数而经过参数推演,称之为函数模板的隐式模板实参调用(隐式调用)。如:

template <typename T> void func(T t){
    cout<<t<<endl;
}
    func(5);//隐式模板实参调用

3.2显示模板实参调用

在发生函数模板的调用时,显示给出模板参数而不需要经过参数推演,称之为函数模板的显示模板实参调用(显示调用)。

显示模板实参调用在参数推演不成功的情况下是有必要的。考察如下程序。

#include <iostream>
using namespace std;
template <typename T> T Max(const T& t1,const T& t2){
    return (t1>t2)?t1:t2;
}

int main(){
    int i=5;
    //cout<<Max(i,‘a‘)<<endl; //无法通过编译
    cout<<Max<int>(i,‘a‘)<<endl; //显示调用,通过编译
}

直接采用函数调用Max(i,’a’)会产生编译错误,因为i和’a’具有不同的数据类型,无法从这两个参数中进行类型推演。而采用Max< int>(i,’a’)调用后,函数模板的实例化不需要经过参数推演,而函数的第二个实参也可以由char转换为int型,从而成功完成函数调用。

编程过程中,建议采用显示模板实参的方式调用函数模板,这样提高了代码的可读性,便于代码的理解和维护。


4.模板特化

4.1模板特化的定义

模板特化不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化。

4.2函数模板特化

函数模板特化是在一个统一的函数模板不能在所有类型实例下正常工作时,需要定义类型参数在实例化为特定类型时函数模板的特定实现版本。查看如下例子。

#include <iostream>
using namespace std;

template<typename T> T Max(T t1,T t2){
    return (t1>t2)?t1:t2;
}

typedef const char* CCP;
template<> CCP Max<CCP>(CCP s1,CCP s2){
    return (strcmp(s1,s2)>0)?s1:s2;
}

int main(){
//调用实例:int Max<int>(int,int)
    int i=Max(10,5);
    //调用显示特化:const char* Max<const char*>(const char*,const char*)
    const char* p=Max<const char*>("very","good");
    cout<<"i:"<<i<<endl;
    cout<<"p:"<<p<<endl;
}

程序正常编译运行结果:

i:10

p:very

在函数模板显示特化定义(Explicit Specialization Definition)中,显示关键字template和一对尖括号<>,然后是函数模板特化的定义。该定义指出了模板名、被用来特化模板的模板实参,以及函数参数表和函数体。在上面的程序中,如果不给出函数模板Max< T>在T为const char*时的特化版本,那么在比较两个字符串的大小时,比较的是字符串的起始地址的大小,而不是字符串的内容在字典序中先后次序。

4.2.1使用函数重载替代函数模板特化

除了定义函数模板特化版本外,还可以直接给出模板函数在特定类型下的重载形式(普通函数)。使用函数重载可以实现函数模板特化的功能,也可以避免函数模板的特定实例的失效。例如,把上面的模板特化可以改成如下重载函数。

typedef const char* CCP;
CCP Max(CCP s1,CCP s2){
    return (strcmp(s1,s2)>0)?s1:s2;
}

程序运行结果和使用函数模板特化相同。但是,使用普通函数重载和使用模板特化还是有不同之处,主要表现在如下两个方面:

(1)如果使用普通重载函数,那么不管是否发生实际的函数调用,都会在目标文件中生成该函数的二进制代码。而如果使用模板的特化版本,除非发生函数调用,否则不会在目标文件中包含特化模板函数的二进制代码。这符合函数模板的“惰性实例化”准则。

(2)如果使用普通重载函数,那么在分离编译模式下,应该在各个源文件中包含重载函数的申明,否则在某些源文件中就会使用模板函数,而不是重载函数。

4.3类模板特化

类模板特化类似于函数模板的特化,即类模板参数在某种特定类型下的具体实现。考察如下代码。

#include <iostream>
using namespace std;

template<typename T>class A{
    T num;
public:
    A(){
        num=T(6.6);
    }
    void print(){
        cout<<"A‘num:"<<num<<endl;
    }
};

template<>class A<char*>{
    char* str;
public:
    A(){
        str="A‘ special definition ";
    }
    void print(){
        cout<<str<<endl;
    }
};

int main(){
    A<int> a1;      //显示模板实参的隐式实例化
    a1.print();
    A<char*> a2;    //使用特化的类模板
    A2.print();
}

程序输出结果如下:

A’num:6

A’ special definition


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008[6.2(P215-P227)]

时间: 2024-10-05 06:44:09

C++模板之隐式实例化、显示实例化、隐式调用、显示调用和模板特化详解的相关文章

Myeclipse Templates详解(一) —— Java模板基础

目录 Templates简介 MyEclipse自带Templates详解 新建Template 自定义Template 因为自己比较懒,尤其是对敲重复代码比较厌恶,所以经常喜欢用快捷键和模板,Myeclipse的模板功能其实很强大,好像自己只用过syso这一个,所以最近学习了一下,初次写博客,谢绝转载. 一.Templates简介 1.1 Templates基本介绍 Myeclipse模板的查看编辑入口,Window->Preferences->Java->Editor->Tem

Baby_Step,Gaint_Step(分析详解+模板)

以下是总结自他人博客资料,以及本人自己的学习经验. [Baby_Step,Gaint_Step定义] 高次同余方程.   BL == N (mod P) 求解最小的L.由于数据范围很大,暴力不行 这里用到baby_step,giant_step算法.意为先小步,后大步. 令L=i*m+j  (m=ceil(sqrt(p-1))), 那么原式化为 B^(i*m)*B^j==N(MOD P)---->B^j===N*B^(-i*m)(MOD P) 我们先预处理B^0,B^1,B^2--B^(m-1)

C++模版基于包含模型之外的显示实例化

一."经典模型"的失效 我们学过C++的人都知道,在C++中组织代码的经典模型是:将函数或类的声明和定义部分分开在不同的文件之中   , 即一般将声明放在一个.h的头文件中而定义在放在一个.cpp文件之中,当然这的确是写代码的一种很优良的风格,但问题 是如果将这种"经典模型"应用到模版上时就会发生连接上错误. 例如: 文件"A.h" #include"iostream" using namespace std; #pragma

Intent 显示意图 隐式意图

//显式意图  :必须指定要激活的组件的完整包名和类名 (应用程序之间耦合在一起) // 一般激活自己应用的组件的时候 采用显示意图  //隐式意图: 只需要指定要动作和数据就可以 ( 好处应用程序之间没有耦合) //激活别人写的应用  隐式意图, 不需要关心对方的包名和类名    public void enter(View view){  String name = et_name.getText().toString().trim();  if(TextUtils.isEmpty(name

【黑马Android】(07)多线程下载的原理/开源项目xutils/显示意图/隐式意图/人品计算器/开启activity获取返回值

多线程下载的原理 司马光砸缸,多开几个小水管,抢救小朋友. import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import

static_cast显示完成隐式转换

用法:static_cast < type-id > ( expression ) 该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性.它主要有如下几种用法: ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换. 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的: 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的. ②用于基本数据类型之间的转换,如把int转换成char,把int转换成

angularjs--控制器的显示与隐示使用

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>13-angularjs--控制器的显示与隐示使用</title> <script src="../js/angularjs.js"></script> <script src="../js/index13.js"><

scala 隐式详解(implicit关键字)

掌握implicit的用法是阅读Spark源码的基础,也是学习Scala其它的开源框架的关键,implicit 可分为: 隐式参数 隐式转换类型 隐式调用函数 1.隐式参数 当我们在定义方法时,可以把最后一个参数列表标记为implicit,表示该组参数是隐式参数.一个方法只会有一个隐式参数列表,置于方法的最后一个参数列表.如果方法有多个隐式参数,只需一个implicit修饰即可. 当调用包含隐式参数的方法是,如果当前上下文中有合适的隐式值,则编译器会自动为改组参数填充合适的值.如果没有编译器会抛

【转】 C++模板详解

C++模板 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数.返回值取得任意类型. 模板是一种对类型进行参数化的工具: 通常有两种形式:函数模板和类模板: 函数模板针对仅参数类型不同的函数: 类模板针对仅数据成员和成员函数类型不同的类. 使用模板的目的就是能够让程序员编写与类型无关的代码.比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型