前面简单介绍了函数模板和类模板,如果模板只是简单的类型参数化,那C++的模板也不过如此,何以独树一帜,迷倒众生,进而衍生出一种全新的程序设计范式呢?如果你写过模板代码或者读过高质量的模板库代码,你是不是有一种感觉,它与通常的OO代码不太一样?
面向对象程序设计通过抽象,通过多态,控制代码在运行时根据实际类型进行路径分发,将软件设计中的不变部分和可变部分进行合理的切分,提高代码的可读性和可维护性,代价是使用了间接性,运行效率多少会有点影响。
如果OO的基石是“动多态”,运行期多态,那泛型的根基就是“静多态”,编译期多态。基于模板的泛型程序设计更多的是针对不同的类型对某一功能或场景的行为定制。模板代码看起来比较短,而且到处都是定义,看不到命令式语言的常用代码结构(分支、循环),看起来有点像声明式编程。问题是它的流程控制、逻辑判断藏到哪里去了?
这些事情的背后是编译器,“编译期多态”。在编译过程中,编译器会根据程序的实际运行需要,主动替你生成代码,挑选那些符合类型要求的代码。你的工作就是定义,定义出最通用的版本,然后按照需求,逐步定义出一个一个的特定类型的版本(唯一的要求是它们都在同一个命名空间里)。之后的任务就交给编译器了,它会认真筛选这些定义,找出最合适的版本。优点是静态绑定的运行效率要高于动态绑定,当然也有缺点,就是代码的编译时间会大大延长(还有就是编译器的实现难度大大增加c++模板类,曾经有人说过,他在实现某个模板特性时的代码超过了之前整个编译器的代码)。
声明式的代码更加符合OC原则(开放-封闭原则,开放扩展,封装修改)。如果后面发现针对某种类型的行为或逻辑需要改动,很容易,那就针对这种类型进行模板的特化(增加新的定义相比修改原有代码逻辑要容易的多,一旦引入问题也更加容易定位)。还有就是C++的核武器 – 元编程,它的基础也是模板的特化。
啰嗦了这么半天,是时候回到代码时间了。
template
class math {
public:
static T max(const T a, const T b) {
return b < a ? a : b; }
};
既然我们已经对max这个函数模板十分熟悉了,这次还是用它吧,脑力嘛,能省就省一点。我们定义了一个类模板math,它只有一个静态函数,功能还是一样的。
math::max(3, 5); // 5
调用没有问题,完美!
int a = 1;
int b = 10;
math::max(&a, &b); // 0x7ffffd4ee8f8
这次返回的是什么东东?哦,不好意思,T这次是int*,返回的是个地址,改一下,解引用嘛,这回没有问题了吗?
*math::max(&a, &b); // 1
不对啊,应该返回10呀,哪里出错了?哦,max的函数体内这次比较的是地址大小,而不是里面的内容大小。怎么办?最好是告诉编译器如果T为指针类型,就比较解引用后的内容大小,如果是其他类型就直接比较大小。之前math类模板的定义,T没有任何限制,可以看成是基本定义(primary),之后,我们可以针对某些特定的类型重新定义类模板,这个称为特化(specialization)。
类模板的特化有两种形式,部分特化(partial specialization)和完全特化(full specialization),它们的差别在于前者仍旧存在类型参数依赖,后者是针对某个特定的类型定义。
template
class math {
public:
static T max(const T *const a, const T *const b)
{ return *b < *a ? *a : *b; }
};
我们针对指针类型进行了部分特化,因为需要用到T,所以template还要引入类型参数T,不过这次在math后面加上了,现在定义的类型是math,相比基本定义math,它更加特殊,更加明确。这回上面的调用就能够返回正确的结果了。
math::max(&a, &b); // 10
过了几天,Boss告诉我说针对std::string类型的max,需要返回字符串长度小的那个。What?没办法c++模板类,Boss总是正确的。
#include
template
class math {
public:
static std::string max(const std::string &str1, const std::string &str2) {
return str1.size() < str2.size() ? str1 : str2;
}
};
这回我们针对特定的类型std::string对math类模板进行了完全特化,template还是需要的,表示这个是针对某个类模板的特化定义,但是类型参数列表为空,这里没有需要依赖的类型参数了,所有类型都是明确的,这次定义的类型是math。
如果将来发现还有类型在适用math::max时的行为与基本版本不一样,你只需要添加新的定义就可以了。编译器在编译代码时会逐个匹配所有定义,从匹配成功的版本中挑选出类型最特殊的那个。如果没有一个匹配,编译器会告诉你未定义;如果有一个以上的定义匹配度一样,编译器会告诉你有歧义。
最后简单总结一下。我们先给出了类模板math的基本定义:math,它适用于所有类型,然后我们针对指针类型进行了部分特化:math,最后对std::string进行了完全特化:math。特化时的类型定义和基本定义的类型参数数量必须一样,只是用更加具体、更加特定的类型形式来替换基本定义。
限时特惠:本站每日持续更新海量设计资源,一年会员只需29.9元,全站资源免费下载
站长微信:ziyuanshu688