# 理解模板编程的隐式接口和编译器多态
面向对象的编程思路以显示的接口和运行期的多态为主要提现。编写一个类数据被封装成private。 对外呈现的函数接口即是显式接口,函数有可能是virtual的。所以在运行期会有一次根据对象动态类型进行调用的多态过程。
面向模板的泛型编程思路和面向对象不一样。它考虑的是使用模板的这个区块,对对象的隐式接口要求。只要对象满足隐式接口的要求,就可以使用这个区块代码。而这个区块代码有可能是个通用的流程。
# 理解模板的使用方法
模板的使用大致有3种场景,一种是函数模板,一种是类模板,还有模板特化
## 函数模板
函数模板的定义形式如下
```
template<typename C>
void printObjName(C obj)
{
std::cout<<obj.getName()<<endl;
}
```
函数模板往往要实现的是一段通用代码,可以给多个类型对象使用。只要这个类型对象满足函数模板的隐式接口要求就可以。函数模板是通过入参具现化的。所以定义的函数模板参数,一定是这个函数的入参之一。
在实际使用过程中, 编译器通过入参的对应关系来具现化函数模板。
## 类模板
函数模板比较好理解。可以理解为一个通用的代码功能段。那么类模板如何理解呢。vector就是一个类模板。他的目的是在连续空间中维护数据,至于数据是啥类型,他不管。所以在编程的时候希望无论什么样的数据都能维护到类里面。这就需要借助模板类。
```
template<typename C>
class DataMgr
{
private:
C data[1000];
....
}
```
类似上述的DataMgr类。他的目的是把通用的管理接口在类模板上只要实现一份。这些接口不关心数据的类型。当这个模板类实际使用具现化的时候,会根据实际类型,具现化不同内存在用的类出来。
模板类需要显示的指定实参,否则编译器没有办法想模板函数那样自动推导出来。
所以上述的模板类在具现化的时候要用类似如下的代码`DataMgr<int> oneIntDataMgr;`
## 模板特化
模板特化是给模板类,模板函数预留的后门。可以理解是用来弥补设计缺陷的。比如你设计了个通用的程序段,本以为这个程序段对所有满足条件的对象都适用。但是最终你发现就是有那么一种对象,需要特殊处理一下下。这时候就需要用到模板特化
```
template<typename C>
void printObjName(C obj)
{
std::cout<<obj.getName()<<endl;
}
template<>
void printObjName<SpecialObj>(SpecialObj obj)
{
std::cout<<obj.Name()<<endl;
}
```
我的问题是与其这样写一个别扭的特化,为什么不直接来一个重载函数`void printObjName(SpecialObj obj)` 如果特化和重载函数同时存在,编译器是什么表现?
这里用vs2019的编译器简单做个实验
```
template<typename C>
void printName(const C&obj){cout<<obj<<endl;}
template<>
void printName<int>(const int&obj){cout<<"这是特化版本调用"<<endl;}
void printName(const int&obj){cout<<"这是普通重载函数"<<endl;}
int main()
{
string obj="stringobj";
printName(obj);
printName(15);
}
```
从上面的实验可以发现当这三个printName同时存在的时候编译器不会报错。编译器优先选择重载函数,其次是选择特化函数,如果这两个都没有,才会用普通的模板参数。
要注意的是普通函数和特化模板函数不广在优先级上有差异。 还有一个差异是,编译器在尝试普通函数的时候会允许隐式转换。但对特化模板函数,是不允许隐式转换的。
网友评论