第十六章 模板与泛型编程

  • 面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况。

    • OOP能处理类型在程序允许之前都未知的情况。
    • 泛型编程在编译时就可以获知类型。

一、定义模板

  • 模板:模板是泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者公式。

1. 函数模板

  • 一个函模板就是一个公式,可用来生成特定类型的函数版本。大致格式为:
template <typename T>
int compare(const T &v1, const T &v2) {}
  • 在模板定义中,模板参数列表不能为空。
  • 模板参数列表的作用很像函数参数列表。函数参数列表定义了若干特定类型的局部变量,但并未指出如何初始化它们。在运行时,调用者提供实参来初始化形参。
  • 模板参数表示在类或者函数定义中用到的类型或值。当使用模板时,我们显式或者隐式地指定模板实参,将其绑定到模板实参上。
  • 模板类型参数:类型参数前必须使用关键字classtypename,这两个关键字含义相同,可以互相使用。旧的程序只能class
  • 非类型模板参数:表示一个值而非一个类型。实参必须是常量表达式。
template <typename T, size_t N>
void array_init(T (&parm)[N]) {}
  • 函数模板可以声明为inlineconstexpr的。如同非模板函数一样。inlineconstexpr说明符放在模板参数列表之后,返回类型之前。
template <typename T>
inline T min(const T&, const T&) {}
  • 模板程序应该尽量减少对实参类型的要求。
  • 函数模板和类模板成员函数的定义通常放在头文件中
  • 模板直到实例化时才会生成代码,这一特性影响了何时才会获知模板内代码的编译错误。通常,编译器会在三个阶段报告错误
    • 第一个阶段:在编译模板本身时,通常只能是检查语法错误。
    • 第二个阶段:在编译器遇到模板使用时,对于函数模板调用,编译器通常会检查实参数目是否正确,以及参数类型是否匹配等,对于类模板调用,编译器可以检查是否提供了正确数目的模板实参。
    • 第三个阶段:在模板实例化时,可以发现类型相关的错误。依赖于编译器如何管理实例化,这些错误可能在链接时才能报告。

2. 类模板

  • 类模板是用来生成类的蓝图的。不同于函数模板,编译器不能为类模板推断模板参数类型。
  • 为了使用类模板,必须在模板名后的尖括号中提供额外信息 —— 用来代替模板参数的模板实参列表。
template <typename T> class Blod {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// 构造函数
Bold();
Bold(std::initializer_list<T> il);
// Bold中的元素数目
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// 添加和删除元素
void push_back(T &&t) { data->push_back(t); }
// 移动版本
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
// 元素访问
T& back();
T& operator[] (size_type i); private:
std::shared_ptr<std::vector<T>> data;
// 若data[i]无效,则抛出msg
void check(size_type i, const std::string &msg) const;
};
  • 实例化类模板:提供显式模板实参列表,来实例化出特定的类。一个类模板的每个实例都形成一个独立的类。
  • 模板形参作用域:模板参数的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。
  • 类模板的成员函数:
template <typename T> ret-type Bold::member-name(parm-list);
  • 默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
  • 在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参。
  • 新标准允许模板将自己的类型参数成为友元。
template <typename T> class Bar{ friend T;}
  • 模板类型别名:因为模板不是一个类型,因此无法定义一个typedef引用一个模板,但是新标准允许我们为类模板定义一个类型别名:
template <typename T> using twin = pair<T, T>;

3. 模板参数

  • 模板参数与作用域:遵循普通的作用域规则,一个模板参数名的可用范围是在其声明之后,至模板声明或定义结束之前。
  • 一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。
  • 当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class
  • 默认模板实参:
// compare有一个默认模板实参less<T>和一个默认函数实参F()
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F()) {
if(f(v1, v2)) return -1;
if(f(v2, v1)) return 1;
return 0;
}

4. 成员模板

  • 成员模板:一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。成员模板不能是虚函数

    • 普通(非模板)类的成员模板
    • 类模板的成员模板

5. 控制实例化

  • 由于在多个文件中实例化相同模板的额外开销可能非常严重,采取显式实例化可以避免这种开销。
  • 显式实例化
    • extern template declaration; //实例化声明
    • template declaration; // 实例化定义
  • 对每个实例化声明,在程序某个位置必须有其显式的实例化定义。
  • 由于实例化定义会实例化所有成员,所以在一个类模板的实例化自定义中,所用类型必须能用于模板的所有成员函数。

6. 效率和灵活性

二、模板实参推断

  • 对于函数模板,编译器利用调用中的函数实参来确定其模板参数。从函数实参来确定模板实参的过程被称为模板实参推导

1. 类型转换与模板类型参数

  • 顶层const无论式在形参中还是实参中,都会被忽略。
  • const转换:可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参。
  • 数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以转换为一个该函数类型的指针。
  • 其他类型转换,例如算术转换、派生类向基类的转换以及用户定义的转换等,都不能应用于函数模板。
  • 注意:将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换数组或函数到指针的转换。
  • 如果函数参数类型不是模板参数,则对实参进行正常的类型转换。

2. 函数模板显式实参

  • 在某些情况下,编译器无法推断出模板实参的类型。
  • 定义
template <typename T1, typename T2, typename T3> T1 sum(T2, T3);
auto val3 = sum<long long>(i, lng); // 使用函数显式实参调用,T1是显式指定,T2和T3都是从函数实参类型推断而来
  • 注意:正常类型转换可以应用于显式指定的实参。

3. 尾置返回类型与类型转换

  • 使用场景:并不清楚返回结果的准确类型,但知道所需类型是和参数相关的。
template <typename It>
auto fun(It beg, It end) -> decltype(*beg)
  • 尾置返回允许我们在参数列表之后声明返回类型。
  • 标准库的类型转换模板,定义在头文件 type_traits 中:
对Mod,其中Mod为 若T为 则Mod::type为
remove_reference X&或X&& X
否则 T
add_const X&或const X或函数 T
否则 const T
add_lvalue_reference X& T
X&& X&
否则 T&
add_rvalue_reference X&或X&& T
否则 T&&
remove_pointer X* X
否则 T
add_pointer X&或X&& X*
否则 T*
make_signed unsigned X X
否则 T
make_unsigned 带符号类型 unsigned X
否则 T
remove_extent X[n] X
否则 T
remove_all_extents X[n1][n2]... X
否则 T

4. 函数指针和实参推断

  • 当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值

5. 模板实参推断和引用

  • 从左值引用函数参数推断类型:若形如T&,则只能传递给它一个左值。但如果const T&,则可以接受一个右值。
  • 从右值引用函数推断类型:若形如T&&,则只能传递给它一个右值。
  • 引用折叠和右值引用参数
    • 规则1:当我们将一个左值传递给函数的右值引用参数,且右值引用指向模板类型参数时(如T&&),编译器会推断模板类型参数为实参的左值引用类型。
    • 规则2:如果我们间接创造一个引用的引用,则这些引用形成了折叠。折叠引用只能应用在间接创造的引用的引用,如类型别名或模板参数。对于一个给定类型X
      • X&&X& &&X&& &都折叠成类型X&
      • 类型X&& &&折叠成X&&
    • 上面两个例外规则导致两个重要结果:
      • 如果一个函数参数就是一个指向模板类型参数的右值引用(如 T&& ),则它可以被绑定到一个左值上。
      • 如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数参数将被实例化为一个左值引用参数( T& )。

6. 理解std::move

  • 从一个左值static_cast到一个右值引用是允许的。
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
return static_cast<typename remove_reference<T>::type&&>(t);
}

7. 转发

  • 使用一个名为forward的新标准设施来传递参数,它能够保持原始实参的类型。
  • 定义在头文件utility中。
  • 必须通过显式模板实参来调用。
  • forward返回显式实参类型的右值引用。即forward的返回类型是T&&

三、重载与模板

  • 多个可行模板:当有多个重载模板对一个调用提供同样好的匹配时,会选择最特例化的版本。
  • 非模板和模板重载:对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,则选择非模板版本。

四、可变参数模板

  • 可变参数模板就是一个接受可变数目参数的模板函数或模板类。
  • 可变数目的参数被称为参数包
    • 模板参数包:标识零个或多个模板参数
    • 函数参数包:表示零个或多个函数参数
  • 用一个省略号来指出一个模板参数或函数参数表示一个包。
// Args是一个模板参数包;rest是一个函数参数包;
// Args表示零个或多个模板类型参数
// rest表示零个或多个函数参数
template <typename T, typename... Args>
void foo(const T &t, const Args& ... rest);
  • sizeof...运算符能返回参数的数目
template<typename ... Args> void g(Args ... args) {
cout << sizeof...(Args) << endl; // 类型参数的数目
coutt << sizeof...(args) << endl; // 函数参数的数目
}

1. 编写可变参数函数模板

  • 当定义可变参数版本的print时,非可变参数版本的声明必须在作用域中。否则,可变参数版本会无限递归。

2. 包扩展

  • 对于一个参数包,除了获取它的大小,唯一能做的事情就是拓展
  • 当拓展一个包时,还要提供用于每个拓展元素的模式
  • 扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。
template <typename T, typename... Args>
ostream & print(ostream &os, const T &t, const Args&... rest) { // 扩展Args
os << t << ", ";
return print(os, rest...); // 扩展rest
}

3. 转发参数包

  • 在C++11中,可以组合使用可变参数模板与forward机制来编写函数,实现将其实参不变地传递给其他函数。

五、模板特例化

  • 定义函数模板特例化:关键字template后面跟一个空尖括号对(<>)。
  • 特例化地本质是实例化一个模板,而不是重载它。特例化不影响函数匹配。
  • 模板将其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,后面是特例化版本。
  • 我们可以部分特例化类模板,但不能部分特例化函数模板。

【C++】《C++ Primer 》第十六章的更多相关文章

  1. C++Primer 第十六章

    //1.模板定义以关键字template开始,后跟一个模板参数列表,此列表不能为空.编译器用推断出的模板参数来实例化一个特定版本的函数.类型参数前必须使用class或者typename(推荐使用typ ...

  2. 《Linux命令行与shell脚本编程大全》 第十六章 学习笔记

    第十六章:创建函数 基本的脚本函数 创建函数 1.用function关键字,后面跟函数名 function name { commands } 2.函数名后面跟空圆括号,标明正在定义一个函数 name ...

  3. Gradle 1.12 翻译——第十六章. 使用文件

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  4. 第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁

    原文:第十六章--处理锁.阻塞和死锁(3)--使用SQLServer Profiler侦测死锁 前言: 作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用.此时,你需要尽快侦测 ...

  5. CSS3秘笈复习:十三章&十四章&十五章&十六章&十七章

    第十三章 1.在使用浮动时,源代码的顺序非常重要.浮动元素的HTML必须处在要包围它的元素的HTML之前. 2.清楚浮动: (1).在外围div的底部添加一个清除元素:clear属性可以防止元素包围浮 ...

  6. Gradle 1.12用户指南翻译——第二十六章. War 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  7. Gradle 1.12用户指南翻译——第三十六章. Sonar Runner 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  8. 《HTTP 权威指南》笔记:第十六章&第十七章 国际化、内容协商与转码

    <HTTP 权威指南>笔记:第十六章 国际化 客户端通过在请求报文中的 Accept-Language 首部和 Accept-Charset 首部来告知服务器:“我理解这些语言.”服务器通 ...

  9. “全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. caffe源码 理解链式法则

    网络结构 首先我们抽象理解下一个网络结构是怎样的,如下图所示 F1,F2,F3为某种函数 input为输入数据,output为输出数据 X1,X2为为中间的层的输入输出数据 总体来说有以下关系 X1 ...

  2. STL——容器(Set & multiset)之 仿函数(函数对象)functor 的用法

    Set/multiset 中元素的存储数据总是会按照从大到小或者从小到大排列,这个是怎么实现的?这就要说 "仿函数" 这个概念了. 仿函数概念 1. 尽管函数指针被广泛用于实现函数 ...

  3. 我叫Mongo,干了「索引探索篇」提升我的效率,值得您拥有

    这是mongo第四篇"索引探索",后续会连续更新4篇 mongodb的文章总结上会有一系列的文章,顺序是先学会怎么用,在学会怎么用好,戒急戒躁,循序渐进,跟着我一起来探索交流.通过 ...

  4. JVM虚拟机(三):Java内存区域

    运行时数据区   Java虚拟机再执行Java程序过程中会把它所管理的内存划分为若干个不同分工的数据区域. 程序计数器   程序计数器时一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示 ...

  5. js处理浏览器兼容

    1.try  catch 在try中执行我们的代码,如果在执行的过程中发生了异常信息,我们在catch中写代替的执行方案 前提:不兼容四位情况下,执行对应的代码,需要发生异常错误才可以检测到 弊端:不 ...

  6. 面试 21-面试题整理 by smyhvae

    21-面试题整理 by smyhvae #JavaScript #存储相关:请描述以下cookie.localStorage.sessionStorage的区别 在H5之前,cookie一直都是本地存 ...

  7. css 05-CSS样式表的继承性和层叠性

    05-CSS样式表的继承性和层叠性 #本文重点 CSS的继承性 CSS的层叠性 计算权重 权重问题大总结 CSS样式表的冲突的总结 权重问题深入 同一个标签,携带了多个类名 !important标记 ...

  8. react第八单元(什么是纯函数-组件的性能优化-pureComponent-组件性能优化的原理)

    课程目标 理解纯函数 熟练掌握组件性能优化的几种技巧 pureComponent和Component的区别 #知识点 一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数 ...

  9. Python进阶——为什么GIL让多线程变得如此鸡肋?

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 做 Python 开发时,想必你肯定听过 GIL,它经常被 Python 程序员吐槽,说 Pytho ...

  10. 获取Web项目中的控制器类以及类中Action方法

    前言 在使用时需要修改命名空间.需要过滤控制器.需要过滤Action方法.结果生成表的插入语句. 代码 public ActionResult ReloadData() { #region 获取所有的 ...