模板与泛型编程

面向对象世界中,只有显式接口和运行期多态(虚函数该被哪个函数所绑定)!泛型编程世界中,仍然有显式接口和运行期多态,但是(参数类型的)隐式接口和编译期多态为特色!
显式接口,编译器通过查看类型的头文件得知;隐式接口,编译器通过模板函数定义可确定参数类型的隐式接口,参数、返回值类型更为宽泛!通过模板函数内的定义(作用于类型参数对象上的方法可确定),但是约束并没有那么强(由于参数类型的不确定),隐式接口返回类型范围更为宽泛,那么在编译时期,根据具体的类型的接口和模板所确定的隐式接口来进行匹配,只有具体化类型的接口包含模板所确定的类型隐式参数需求编译通过,否则编译不过。
运行期间的多态,所执行的函数由实际指向的对象决定!编译期间多态(有点类似函数重载,在编译期间决定所执行的函数),由于具现化类型不同,导致调用函数也不同!
在这里插入图片描述在这里插入图片描述item42:understand the two meanings of typename.
typename两处用法:
1、用于template声明式中等同于使用class
**在这里插入图片描述**
2、使用在嵌套类型参数名称前
本条例解决如下图的编译问题:
在这里插入图片描述
template内可使用的两种名称:从属名称和非从属名称。名称依赖于类型参数,称为从属名称,再细化一些,若是一个依赖于类型参数的嵌套名称,则称之为嵌套从属名称;名称与参数类型无关,称之为非从属名称。嵌套从属名称为解析过程(编译前期,此时不知道具体的参数类型)带来困难。
问题的根源在于:解析器在解析名称时,无法得知C::const_iterator是个什么东西?类型?静态变量?为了去除这种不确定性,解析器默认将嵌套名称不视为类型,因此下面的模板函数无法通过编译。
在这里插入图片描述再来看个稍微复杂一些的从属嵌套名称,value_type和const_iterator都是嵌套从属名称,我们编写程序时知道从属嵌套名称是个类型,但解析器不知道,解析器的设计者通过设计使用typename可使解析器得知嵌套从属名称是个类型,因此可使用typename将一个从属名称指示为类型:
在这里插入图片描述
在这里插入图片描述
修正一下:
在这里插入图片描述
但是凡事都有例外,对于嵌套名称使用来说,在书写基类列表时、书写成员初始化列表时,嵌套名称前不可以加typename。
在这里插入图片描述
typename用在嵌套从属名称前时,常常与typedef搭配使用!嵌套从属名称太长了,每次写岂不麻烦?!这就带来了typedef与typename常常共同使用。
在这里插入图片描述
在这里插入图片描述
item43:know how to access names in templatized base class.
问题源于继承一个基类模板!基类模板由于可能存在特例化模板,这使得在派生类的调用基类方法与普通基类不同!
编译器解析template的声明式属于编译早期;具体化template属于编译晚期
模板类功能是一致的,但是可能类型不一样,接口实现也是不一样的!对于特殊的类型,我们总是以特例化的方式指出针对该类型的模板具体化,不使用通用模板类而是使用针对该类型的特例化模板进行具体化!这正是特例化模板类的用武之地。特例化模板写法:template<>表示非class 非template,而是一个特例化的template类。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
模板类继承模板类时我们来分析一下解析器、编译器为我们做的事,解析期并不知道类型参数的具体类型,为了安全起见,解析器是不允许我们在类中使用任何基类中方法,因为对于模板的具体化,可能根据类型参数我们会选择不同的模板类(解析器知道有可能存在特例化模板,通常特例化模板和通用模板实现接口和方法不同),针对不同的类型参数,接口可能不一样。因此为了避免这个问题,编译器不允许我们以原来的方式(具体类)直接调用基类的接口。
和解决嵌套从属名称思路一样,除非我们以编译器解析器认定的方式去调用接口(这表明我们知道这个方法是所有类型都包含的,无论是否为特例化类型),有三种方法!但是在运行期间,具体化的类型不支持模板中调用的函数仍旧会被检测出来!
1、使用this指针
在这里插入图片描述
2、using 声明(在面向对象思想中,我们也使用过using声明使得函数在当前类的作用域内可见以避免基类方法被派生类方法所遮掩,而在这里我们只想在编译前期告诉解析器无论模板类时特例化的还是通用的,方法均存在)
在这里插入图片描述
与普通class内使用using声明的对比:
写法相同目的不同,一个是为了告知编译器在模板基类存在这个方法;一个是为了使得在类的作用域内使得方法可见可被调用:
在这里插入图片描述

3、类作用域,但是若是虚函数,则无法实现多态性(不建议使用)
在这里插入图片描述
编译晚期发现实例化后的基类中不包含需要调用方法依旧产生编译错误,特例化基类中不包含sendclear方法:
在这里插入图片描述
在这里插入图片描述

item44:factor parameter-independent code out of templates
主要讨论非类型参数引起的代码膨胀!通常解决方法是将引起膨胀的非类型参数拆出来成为新类的数据成员或是函数参数。
解决template带来的代码膨胀问题:共性与变性分析,在编写函数或是class的时候,将相同的部分令为一个函数或是类,通过调用公共函数或是(继承)复合class形成新的类。
来看一个具体引起代码膨胀的例子,invert函数除了大小不一样外,其余部分完全相同,但由于非类型参数的不同,导致具现化时产生两份代码,加之具现化时参数类型一样,导致代码冗余:
在这里插入图片描述
问题关键就是非类型参数,那何不将这个函数单独拿出来放在一个新的类内,这个新类存在的价值就是帮助另一个类的实现,加之将方法定义为了保护成员所以采用了私有继承方法,新类的类型参数只有元素类型,可以说相同类型的元素共享一个基类,一个函数(以矩阵大小作为函数的参数),这样就不会带来代码膨胀的问题。
在这里插入图片描述基类需要操作实际的矩阵,那么派生类需要告知矩阵的存储位置,仍可以通过函数参数传递指针(指向矩阵在内存的位置),但如果这样的函数很多,那么次次传递指针岂不有点麻烦?何不在类中设置一个指针成员,指向矩阵。
在这里插入图片描述
派生类(矩阵接口类)的可能实现:
在这里插入图片描述
动态内存分配,矩阵存到heap中:
在这里插入图片描述
在这里插入图片描述item45:use member function templates to accept “all compatible types”
模板类中仍可以定义模板成员函数,明显地,一个具现化后的模板类可以有多个具体的成员函数。成员模板函数的意义在于具现化后的类有多个类型不同实现相同的成员函数!
模板的类型参数存在base-derived关系时,具现化的模板对象并不存在base-derived关系。如何使得具现化后的模板对象也存在类型参数之间的base-derived关系是这条准则将着力解决的问题!(为什么需要具现化后模板对象也存在base-derived关系,为了方便做隐式转换)
以一个例子来说明这个问题:
类型之间存在继承关系:
在这里插入图片描述
设计一个智能指针类,并以上述类型作为类型参数具现化智能指针,此时无论具体的参数类型是否存在继承关系,我们所具现化后的智能指针对象间并不存在继承关系,因此做隐式类型转化是无法通过编译的:
在这里插入图片描述
观察可知,我们希望根据Middle类型参数化的智能指针来拷贝构造出一个Top类型参数的智能智能,用户的需求是无止境的,难道没加入一个派生类就的加入一个拷贝构造函数吗?不!多个拷贝构造函数,想起了什么!必须是模板函数啊!成员模板函数登场了~~~~~这里的拷贝函数成为泛化copy函数,与普通copy不是一件事,没有任何构造函数,但有泛化copy函数时,编译器仍会生成copy构造函数!
在这里插入图片描述
但我们需要对这个成员模板函数的实现加以限制!不然,会出现逻辑混乱,比如:支持从基类对象隐式转换为派生类对象或是支持从int * 转化为double *,编译器并不支持此类的转化。为此,我们需要设计拷贝函数,使其满足符合类型参数隐式转化的条件时智能指针之间才能发生隐式转化。解决方法就是利用智能指针实际包含的原始指针加以限制,原始指针类型能否发生隐式转化在编译期间是可知的。
在这里插入图片描述
下面来看看shared-ptr模板类所支持的构造函数,支持隐式类型转化泛型的copy函数,普通的copy函数,以及其他智能指针类型的copy构造函数:
在这里插入图片描述在这里插入图片描述
item47:use traits classes for information about types
开篇以下面的例子为引,设计一个template函数能改变迭代器值,在这个模板函数中我们需要知道迭代器所属的类型,针对不同的迭代器类型,所进行的操作也是不同的,为此我们这里提出了traits classes以在编译器获取类型信息。
在这里插入图片描述
我们所希望设计的函数大概实现如下,接下来就来想办法获取迭代器类型吧:
在这里插入图片描述首先在设计容器时需要创建一个所属的迭代器,且要有一个成员来表明迭代器所属类型。
在这里插入图片描述迭代器都有一个iterator_category类型,那么我们来创建模板iterator_traits类吧,取出迭代器的数据
成员即可,但是若类型参数所接受的不是迭代器类型,而是一个普通指针该怎么办?template特例化!
在这里插入图片描述
在这里插入图片描述
有了iterator_traits的定义后,那就来使用iterator_class吧!我们试图给出一种advanced方法实现,这个方法首先编译就不会通过,因为在编译器会保证所有的源码都是有效的,在这里+=运算符只能用在random_iterator上,对于其他类型的迭代器都是不支持的,编译就不过;此外,if…else语句是在运行期执行的,而其实在编译期我们就已经得知确切的参数类型了,若是使用if…else我们无法精简代码,能不能不使用if…else,使用其他方法在编译期就来根据确切的迭代器类型选择做不同的事情:
在这里插入图片描述
函数重载被称为编译期条件句!为什么这么说呢?函数调用的时候,实参与形参的匹配发生在编译期,寻找实参形参间的最佳匹配不就是个条件判断,因此可以说是编译期条件句!

现在我们设置一系列的重载函数,与advance函数相比呢,多了一个参数,正是利用这个参数进行了advance方法的条件判断功能,将advance不同条件下执行操作放到这些个重载函数中!
在这里插入图片描述
advance的实现就是调用doadvance,可以说利用重载函数来辅助真正意义上的函数实现!而且不用条件判断语句,就是利用编译期间的函数匹配来达到条件判断的效果!
在这里插入图片描述在这里插入图片描述
在这里吧,我们主要是说了traits表现类属性的一个极小的实现iterator_traits,除了template iterator_traits外,还有char_traits、numeric_limits来表现类属性。
在这里插入图片描述
在这里插入图片描述item48:be aware of template metaprogramming.
了解甚少,之后再来补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值