-
模板编程基础 (虽然模板函数和函数模板之类概念容易混淆但是没有什么别的办法所以还是拿来用) (函数模板、类模板成员函数、类模板成员函数模板和非模板函数是在模板编程中可能出现的函数,但是这只是为了表现模板编程使程序的理解变得困难) (这里和之前不一样,涉及指针和引用会少些,但是注意理解“引用是天然常量”,后文会指出)
template<typename T>class MyClass;//这是类声明 template<typename T> class MyClass{ T func(...);//类定义,但成员函数声明 T func(...){...}//类定义,内联成员函数 template<typename T2> T func(T2);//类定义,成员函数模板声明 ... };//类定义 template<typename T> T MyClass<T>::func(...){...}//成员函数模板定义 template<typename T> template<typename T2> T MyClass<T>::func(T2){...}//类成员函数模板定义 template<typename T> T func(T){...}//自由函数模板定义 int func(int){...}//非模板函数定义 //由于模板编程的不同物理设计方式将导致声明和定义手段不同,实际上会更加复杂化。
-
模板参数和调用参数: 对于一个函数模板和它的实例化结果,都存在模板参数和调用参数,且都存在形参实参的区分。对于一个类模板,如果它有成员函数,那么也存在调用参数。
-
类型模板参数、非类型模板参数和模板的类模板参数:
template<typename T>//T是类型名 template<int VAL>//VAL是变量 template<template<typename ELEM>class CONT>//CONT是一个类模板,在模板中使用它需要加<>
类型模板参数可以是模板类。 非类型模板参数只能是整型、枚举和指针,而绝不能是引用。且通常只支持字面量,甚至不支持临时量和常量。因为在编译时就要完成模板参数的替换。 模板的类模板参数传入一个模板名。由于不支持函数模板参数所以会把模板的类模板参数简称为模板的模板参数。
-
调用参数 这主要指的是函数模板的函数头中的参数。也包括类模板的成员函数头。
-
实际参数
-
对于函数模板,其使用过程通常不涉及显式给出实际模板参数,这通常通过推断来完成。推断依据是实际调用参数。
-
对于类模板,其使用通常要显式给出实际模板参数而不会使用推断,因为能推断的类型不如用auto。
-
-
参数缺省 调用参数缺省和原来一致。 模板参数缺省规则和调用参数类似,但是在模板参数缺省值中常用未缺省的模板参数,该写法常见且有效。
template<typename T,typename _ALLOC = std::allocator<T>> template<typename T,typename _CONT = std::vector<T>> template<typename T,typename _JUDGE = std::less<T>>
-
-
模板特化 模板特化是创建对应特定模板参数的模板实例。特化可以与原模板实例化结果完全不同。
-
函数模板特化:
template<typename T> T func(T){...};//主函数模板 template<> int func<int>(int){...};//完全特化,且绝对不要在函数特化中改变接口,这不好。 template<typename T> T func(T*){...}//函数模板重载,但是效果类似类模板的部分特化,当调用参数为指针时将优先用这个。
-
类模板特化 类模板特化比较复杂,这是由于类模板实例化的特点。
template<typename T1,typename T2>class MyClass{...};//主类模板 template<typename T>class MyClass<T,T>{...};//部分特化 template<>class MyClass<int,double>{...};//完全特化
特化语法上可以做到实现完全不同,但是语义上应该做到接口一致。 特化可以不实现一部分接口成员函数,这样特化模板的实例将不具有这些接口。 特化的进化版本包括if constexpr、概念约束、标签分发。特化实在是挺糟糕一特性所以其实不要多用的好。(比如离谱的
std::vector<bool>
)
-
-
模板参数推断、选用重载函数 (有个高端名字叫实参的演绎) 最优先使用非模板函数。 选用函数模板时,根据调用参数的类型,会推断出合适的实际模板参数在函数体中替代,即实例化。 不会进行隐式类型转换,所以匹配不了就会报错。 选择模板若能与特化模板匹配,会优先选择特化模板。 (还有很多记不住一点)
max(...);//从非模板函数、函数模板中选择 max<>(...);//仅从函数模板中选择 max<int>(...);//选择用int实例化的模板函数,相当于正常函数调用,没有类型推断,所以会隐式类型转换
注意当提供的实际模板参数不足时,剩余的模板参数会进行推断,此时相当于选用部分特化模板。
-
模板的实例化和实例化的结果:
-
模板实例化都是在编译时进行的。所以如果编译单元内部找不到已实例化的模板,链接时就只能使用其他编译单元中已实例化的模板,而不能按需再实例化。
-
类模板的实例化得到模板类,模板参数不同的模板类不属于同一个类。函数模板实例化得到模板函数,模板函数之间是重载的关系,且同名函数模板、模板函数和同名非模板函数之间也是重载关系。特化模板与主模板也可以看成是这样。
-
类的实例化不是一步完成的。对于一个类模板的实例,其静态成员、内联成员将在最开始实例化,但是其他成员将在存在调用语句时才被初始化。这使得一个类型即使没有类模板所需要的所有接口,也能用于类的模板参数,除非调用了不存在的接口导致“类型没有成员”出错。 (因此c++17加入的if constexpr、c++20标准加入的概念约束都用于确定实际模板参数存在该接口,在此之前只能通过哑对象、哑函数的方式进行)
-
-
-
typename、const、using 这些是会被标成蓝色的保留字。在模板编程中有一些特殊用法。
-
typename
template<typename T> ... typename T::iterator it; ... template<typename T> ... (typename std::remove_reference<T>::type&){...}
这事实上是个定义语句,定义了一个T::iterator类型变量。但是它需要typename,原因是真没看懂。
-
const const后置在模板编程中有相当重要的作用。 通常const后置不改变类型,如:
-
const int = int const
-
const int& = int const&
但当指针要加修饰符时将会改变。通常用从右到左的读法解读指针:
-
const int* ptr = int const* ptr,ptr是指向const int的指针(ptr is a * of const int/int const)
-
int* const ptr,ptr是指向int的常指针(ptr is a const* of int)
-
const int* const ptr = int const* const ptr,ptr是指向const int的常指针(ptr is a const* of const int)
引用虽然与指针有关,但是一个引用对应的就是一个常指针,所以没有
T&const
的写法,即引用天然是常量。const后置主要是为了解决实际模板参数是常函数指针/引用之类的情况,因为常函数的const后置了。
template<typename T> ... (T const& func){...}
-
-
using 这玩意是c++14(或者17?)用来代替typedef的。所以建议是舍弃typedef,因为它确实比using难用。
using CPTR = const int*;//模板外使用 template<typename T> ... using iterator = iterator<T>;//模板内使用是using独有的 ...
-
-
完美转发
这个完美其实主要是保留实际调用参数的引用类型,用来返回值或者干什么别的,因为现代c++还是希望少用指针。
完美转发功能也通常是用在函数模板上的。
-
万能引用和引用折叠
template<typename T> ... (T&&){...}
类型推导时,如果实际调用参数为左值(持久变量或T&),则T推导为T&,此时参数类型
T& &&
发生引用折叠取T&;当实际调用参数为右值(临时变量、字面量等或T&&),则T推导为T,此时只是T&&
右值。-
不管实际调用参数是左值还是右值,
T&&
都能被同类型代替,但T不能。 -
当实际调用参数为左值时,T为T&;反之为T。
-
-
std::forward()
和std::move()
-
T作为标志,将传入的参数都修改为目标的引用,这就是完美转发,通过
std::forward()
实现。template<typename T> T&& std::forward(std::remove_reference<T>::type& arg){ return std::static_cast<T&&>(arg); } /* * 使用时T是显式模板参数。 * 当T是Mytype&时,调用参数被强制转换为Mytype&类型返回。 * 当T时Mytype时,被强制转换为Mytype&&类型返回。 */
在实际使用时
std::forward()
只用在模板中,用于处理模板参数包居多。 -
std::move()
这会创建一个右值引用,参数一定是一个持久变量。使用
std::move
是移动语义中最重要的内容。-
当右值引用被赋值给一个普通类型时将会触发移动,而未发生该行为则不会结束持久变量的生命,但是具有将要结束的语义。
-
当用持久变量想触发移动语义时,请在函数实参中对持久变量做
std::move
。字面量、临时量等右值不需要。
Class MyClass{ public: MyClass() = default; MyClass(const MyClass&){...};//拷贝构造 MyClass(MyClass&&)noexcept{...};//移动构造 MyClass& operator=(const MyClass&){...}//拷贝赋值 MyClass& operator=(MyClass&&)noexcept{...}//移动赋值 }; MyClass(MyClass());//移动 MyClass myClass; MyClass(myClass);//拷贝 MyClass(std::move(myClass));//移动,此后myClass无效
通常要定义一个移动语义为主的类,需要给移动构造、移动赋值加上noexcept修饰;而仅移动语义还需要删除拷贝构造和拷贝赋值。
-
-
-
(接下来就是找使用了完美转发的示例了)
comment 评论区
star_outline 咱快来抢个沙发吧!