特例化介绍
模板特例化主要是用于在模板特定情况下的一些特殊定义,用来完善模板在特定情况的调用
我们先实现一个函数模板
1 | template <typename T> |
接下来我们实现一个带字面值常量的特例化版本
1 | //带字面常量的比较函数 |
我们实现一个testcompare函数测试普通版和特例话版本的函数调用
1 | void testcompare() |
当调用特例化版本时,N会被设定为”h1”的长度,M会被设定为”mom”长度。
但是我们发现使用通用模板类型的函数compare在被叫指针p1和p2时不是很理想,可以单独实现针对p1和p2指针特定版的模板函数
1 | template <> |
对于char * 版本我们实现了自己的比较规则,如果长度长的那个就是大值,相等则依次比较字符串中的每个字符。
类模板
类模板的使用和函数模板类似,我们先声明两个模板类,然后为模板类声明一个比较函数重载运算符
1 | template <typename> |
我们实现Blob<T>
模板类
1 | //定义模板类型的blob |
接下来实现Blob模板类的几个成员函数
1 | template <typename T> |
实现了pop_back, back, check等操作,以及下标索引等函数,接下来实现比较运算符重载
1 | template <typename T> |
此时我们还没有实现迭代器版本的构造函数,与类模板的普通成员函数不同,成员函数有自己的模板,所以要写两个模板名
1 | //与模板类的普通成员不同,成员模板是函数模板 |
这样我们就可以通不同类型的vector初始化Blob的构造函数了
1 | void use_tempmemfunc() |
接下来我们实现BlobPtr这个模板类
1 | template <typename T> |
BlobPtr实现了根据Blob构造自己的成员wptr以及curr,因为wptr是一个弱指针,所以只做弱关联。
接下来我们实现前置++和后置++
1 | template <typename T> |
前置++很容易理解,后置++理解较为困难,这里做一下说明,后置++的函数里先用一个BlobPtr引用类型的临时变量rt存储了*this,因为this不会被释放,所以rt就是*this
的引用,所引用的内容不会释放,这样外界接受到rt后同样是引用的*this
。这样即使rt被回收了也没关系,因为外部已经捕获到*this
的引用了。然后对curr++操作,这就是我们看到的先返回*this
的引用,后++。
模板类没有实现拷贝赋值时,默认用拷贝构造完成构造初始化
1 | void use_classtemp() |
模板的友元
模板类也支持友元类的访问,以下列举了几种情况
1 | template <typename T> |
类模板的别名
类模板的别名定义有以下几种方式
1 | //定义模板类别名 |
类模板的静态成员
类模板的静态成员要在非内联文件中初始化,也就是说在类模板声明的.h文件初始化。
1 | //类模板的static成员 |
告知编译器模板的子类型
对于string::size_type , size_type是一个类型
默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。
因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。
我们通过使用关键字typename来实现这一点:
我们用下面的例子显示指名模板名作用域下的是类型不是名字
1 | // 用typename 告知编译器T::value_type是一个类型 |
巧用模板类完成析构
有时候我们可以利用模板类型实现()的重载,这样通过仿函数传递给智能指针的第二个参数,可以帮助智能指针回收内存
1 | //函数对象,给指定类型的指针执行析构 |
DebugDelete实现了仿函数,接下来写一个函数调用这个仿函数
1 | void use_debugdel() |
模板类型推断
有时候对于模板函数返回的类型表示起来很复杂时,可以通过auto 配合尾置类型推断返回数据类型
比如我们我们想返回迭代器指向类型的引用
1 | //推断返回类型,通过尾置返回允许我们在参数列表之后的声明返回类型 |
通过decltype(*beg)返回迭代器beg指向的元素的引用类型。
如果想要返回指向元素的副本类型,不是引用类型可以通过remove_reference去引用
1 | // remove_reference 是一个模板 |
模板的左值和右值
函数模板同样存在左值和右值
1 | //接受左值引用的模板函数 |
f2(42) T就被推断为int
int i = 100; f2(i) T就被推断为int& 参数类型就变为int& &&
当模板函数的参数是一个T类型的右值引用
1 传递给该参数的实参是一个右值时,T就是该右值类型
2 传递给该参数的实参是一个左值时,T就是该左值引用类型
折叠规则
X&& 、X&&& 都会被折叠为X&
X&& && 会被折叠为X&&
所以我们可以推断move的实现原理,其参数一定是T&&类型,因为其能接受左值和右值两种类型。其返回值一定是实参类型的右值引用类型。
1 | template <typename T> |
为什么要有原样转发
stl::forward是用来做原样转发的,将原有类型保持原样传递给其他函数,这种机制尤为重要。因为如果不进行原样转发,传递的参数变为左值,传递给一个接受右值引用的函数会出现编译报错。
比如我们实现一个flip函数,既能接受左值又能接受右值,并且在函数内部修改这个值会同步到外部实参的效果,那他的实现一定是通过模板类型T&&实现的,通过折叠达到适配左值和右值的目的
1 | void flip(F f, T1 &&t1, T2 &&t2) |
flip 函数内部调用了函数f, 将t1和t2的类型原样转发。
1 | void gtemp(int &&i, int &j) |
总结
本文模拟实现了vector的功能。
视频链接https://www.bilibili.com/video/BV15t4y1W7ZL/?vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9
源码链接 https://gitee.com/secondtonone1/cpplearn