C++ Templates (1.1 初窥函数模板 A First Look at Function Templates)
1.1 初窥函数模板 A First Look at Function Templates
函数模板提供了一种针对不同类型的可以被调用的函数行为,换句话说,一个函数模板代表一系列的函数。函数模板和普通函数很像,除了函数的一部分元素有待确定:这些元素是参数化的(parameterized)。为了方便描述,首先看一个简单的例子。
1.1.1 定义模板 Defining the Template
以下是一个返回两个值中最大值的函数模板:
// basics/max1.hpp
template <typename T>
T max(T a, T b)
{
// if b<a then yield "a" else yield "b"
return b < a ? a : b;
}
该模板定义指定了一系列返回两个值中最大值的函数,这两个值作为函数参数a和b进行传递[1]。这些参数的类型由模板参数(template parameter)T确定。如该例子中所见,模板参数必须通过如下的语法形式进行声明:
template <comma-separated-list-of-parameters(用逗号隔开的参数列表)>
本例中,(模板)参数列表为typename T
。符号<和>为一对尖括号(angle brackets),请注意该符号的使用。关键字typename引入了一个类型参数(type parameter)。虽然这是到目前为止最常用的一种C++程序模板参数,但其他的参数也是有可能的,将在后续章节中讨论(详见第3章)。
该例中,类型参数为T,可以使用任何标识符(identifier)作为参数名字,但使用T是典型的方式。类型参数代表任何一种参数,并且由调用者调用该函数时确定。可以使用任何类型(基础类型(fundamental type)、类(class)等等),只要该类型提供了模板中使用的操作(operation)。本例中,类型T必须支持<操作,因为a和b通过该操作符(operator)进行比较。可能不太明显的是,根据max()
函数的定义,类型T的值必须是能够拷贝的(copyable),以使得该值能够被返[2]。
由于历史原因,也可以使用关键字class代替typename来定义一个类型参数。关键字typename出现相对晚一些,在C++98标准进化的过程中出现。在那之前,关键字class是引入类型参数的唯一方法,并且该方法依然保持有效。因此,模板函数max()
可以等效地被定义成如下形式:
template <class T>
T max(T a, T b)
{
return b < a ? a : b;
}
该情形下,这两种定义没有任何区别。因此,尽管使用关键字class,任何类型可以作为模板参数。然而,此处使用关键字class会有误导性(不是只有class的类型才能够用于替换T,基本类型也可以),因此更建议使用关键字typename。不幸的是,不像类类型声明(class type declaration),当声明类型参数时关键字struct不能用于替换typename。
1.1.2 使用模板 Using the Template
以下程序展示了如何使用max()
函数模板:
// basics/max1.cpp
#include "max1.hpp"
#include <iostream>
#include <string>
int main()
{
int i = 42;
std::cout << "max(7,i): " << ::max(7,i) << '\n';
double f1 = 3.4;
double f2 = -6.7;
std:cout << "max(f1,f2): " << ::max(f1,f2) << '\n';
std::string s1 = "mathematics";
std::string s2 = "math";
std::cout << "max(s1,s2) " << ::max(s1,s2) << '\n';
}
在该程序中,函数max()
被调用三次:一次使用两个int,一次使用两个double,和一次使用两个std::string。每一次,最大值被计算,该程序由如下输出
max(7,i): 42
max(f1,f2): 3.4
max(s1,s2): mathematics
注意到每一次max()
模板被调用时,都带由::
,这用于确保我们的max()
模板在全局命名空间中被找到。在标准库中有一个std::max()
模板,在一定条件下可能被调用或者引起不明确(ambiguity)[3]。
模板不会被编译成一个可以处理任何类型的单个实体。相反,针对模板使用的每一个类型,对应每一个类型的不同实体从模板中生成[4]。因此,max()
为三个类型分别进行编译。比如,第一个max()
调用
int i = 42;
... max(7,i)...
使用int作为模板参数T的函数模板。因此,它具有调用如下代码的语义:
int max(int a, int b)
{
return b < a ? a : b;
}
这个使用实体类型代替模板参数的过程称为实例化(instantiation),并生成模板的实例(instance)[5]。
仅仅使用函数模板便可触发一个实例化的过程,并不需要额外单独请求实例化。
相似地,其他对max()
的调用也将实例化double和std::string版的max模板,就像它们被分别以如下方式声明和实现:
double max(double, double);
std::string max(std::string, std::string);
void也可以作为有效的模板实参,只要生成的代码是有效的即可。比如:
template <typename T>
T foo(T*)
{
}
void* vp = nullptr;
foo(vp); //OK: 将推断出 void foo(void*);
1.1.3 二阶段翻译(二次翻译) Two-Phase Translation
如果用某个类实例化模板,但该类不支持模板中所有操作将导致编译期错误(compile-time error),比如:
std::complex<float> c1, c2; //未提供<操作
...
::max(c1,c2); //编译期错误
模板的“编译”有两个阶段:
定义时(definition time)不进行实例化,模板代码的正确性将被检查(但不考虑模板参数),这包括
发现语法错误,比如丢失分号等;
发现使用不依赖于模板参数的未知名字(类型名、函数名...);
检查不依赖于模板参数的静态断言(static assertion);
实例化期间(instantiation time),模板代码再一次被检查,以确保所有的代码是有效的。也就是说,所有依赖于模板参数的部分被检查了两遍(double-checked)。
举个例子:
template <typename T>
void foo(T t)
{
undeclared(); //如果undeclared()是未知的,将产生第一阶段编译器错误(first-phase compile-time error)
undeclared(t); //如果undeclared(T)是未知的,将产生第二阶段编译错误(second-phase compile-time error)
static_assert(sizeof(int) > 10, "int too small"); //如果sizeof(int)<=10,将一直失败
static_assert(sizeof(int) > 10, "T too small"); //如果使用sizeof(T)<=10的类型T进行实例化,将失败
}
名字被检查两遍的过程被称为二阶段检查/二次检查(two-phase lookup),该过程将在第14.3.1节中详细讨论。
一些编译器在第一阶段不执行全面检查(full check),因此在模板代码至少进行一次实例化前无法发现一般的问题[6]。
编译和链接
在实践中处理模板过程中,二阶段翻译将导致一个重要的问题:当一个函数模板被使用并触发实例化时,编译器(有时候)需要看模板的定义。这破坏了普通函数(ordinary function)的常规编译和链接之间的差别,即在编译期只需要函数的声明(the declaration of a function)便可编译使用函数的代码。处理该问题的方法在第9章中进行讨论。此时,可以使用最简单的方法:在头文件中实现每一个模板。
脚注
注意到
max()
模板依据[StepanovNotes]有意地返回“b < a ? a : b”而不是“a < b ? b : a”来确保函数正确,尽管这两个值相等但并不等价(equivalent but not equal)。进一步讨论可参考知乎:b < a ? a : b 和 a < b ? b : a 有什么不同?和博客园:为什么 max() 应该写成 b < a ? a : b 呢? ︎C++17以前,类型T必须是可拷贝的,才能进行参数传递给函数。但是自从C++17,可以传递临时量(temporaries)(右值,见附录B)尽管没有有效的拷贝构造和移动构造。 ︎
比如说,如果一种参数类型在std命名空间中被定义(如std::string),根据C++查找规则,全局的和std命名空间中的
max()
都可以被发现(参见附录C)。 ︎“一个实体适用于所有类型”的方案是可信的,但是实践中没有被采用(这将导致运行时效率降低)。所有的语言规则都是基于一个原则:不同的模板参数生成不同的实体。 ︎
在面向对象编程中,术语实例和实例化也被使用,指一个类的实例(concrete object of a class)。然而由于此书关于模板,该术语指使用模板的语境,除非特殊说明。 ︎
比如说,一些版本的Visual C++的编译器(如Visual Studio 2013 和 2015)允许不依赖于模板参数的未声明的名字,甚至允许一些语法缺陷(syntax flaws),比如丢失分号。 ︎
C++ Templates (1.1 初窥函数模板 A First Look at Function Templates)的更多相关文章
- C++ Templates (1.5 重载函数模板 Overloading Function Templates)
返回完整目录 目录 1.5 重载函数模板 Overloading Function Templates 1.5 重载函数模板 Overloading Function Templates 和普通函数一 ...
- Scrapy001-框架初窥
Scrapy001-框架初窥 @(Spider)[POSTS] 1.Scrapy简介 Scrapy是一个应用于抓取.提取.处理.存储等网站数据的框架(类似Django). 应用: 数据挖掘 信息处理 ...
- 初窥Flask
初窥Flask Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求 ...
- 初窥Kaggle竞赛
初窥Kaggle竞赛 原文地址: https://www.dataquest.io/mission/74/getting-started-with-kaggle 1: Kaggle竞赛 我们接下来将要 ...
- scrapy2_初窥Scrapy
递归知识:oop,xpath,jsp,items,pipline等专业网络知识,初级水平并不是很scrapy,可以从简单模块自己写. 初窥Scrapy Scrapy是一个为了爬取网站数据,提取结构性数 ...
- Effective C++ -----条款45:运用成员函数模板接受所有兼容类型
请使用member function templates(成员函数模板)生成”可接受所有兼容类型“的函数. 如果你声明member templates 用于“泛化copy构造”或“泛化assignme ...
- 读书笔记_Effective_C++_条款四十五:运用成员函数模板接受所有兼容类型
比如有一个Base类和一个Derived类,像下面这样: class BaseClass {…}; class DerivedClass : public BaseClass {…}; 因为是父类与子 ...
- 网页3D效果库Three.js初窥
网页3D效果库Three.js初窥 背景 一直想研究下web页面的3D效果,最后选择了一个比较的成熟的框架Three.js下手 ThreeJs官网 ThreeJs-github; 接下来我会陆续翻译 ...
- iOS视频直播初窥:高仿<喵播APP>
视频直播初窥 视频直播,可以分为 采集,前处理,编码,传输, 服务器处理,解码,渲染 采集: iOS系统因为软硬件种类不多, 硬件适配性比较好, 所以比较简单. 而Android端市面上机型众多, 要 ...
随机推荐
- ElasticSearch(二)Kibana、版本控制
Kibana简介: Kibana可视化界面 Kibana是一个开源的分析和可视化平台,设计用于和Elasticsearch一起工作. 你用Kibana来搜索,查看,并和存储在Elasticsearch ...
- python新手70个练手项目
不管学习哪门语言都希望能做出实际的东西来,这个实际的东西当然就是项目啦,不用多说大家都知道学编程语言一定要做项目才行. 这里整理了70个Python实战项目列表,都有完整且详细的教程,你可以从中选择自 ...
- raw目录的位置是D:\android_projects\qrscan\app\src\main\res\raw
D:\android_projects\qrscan\app\src\main\res\raw 这里可以放数据库文件和音频文件 文件名为sp.mp3 引用方法: MediaPlayer mp = Me ...
- 在ASP.NET Core中创建自定义端点可视化图
在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...
- 从element-ui按需引入去探索
element-ui的按需引入的配置:文档地址 npm install babel-plugin-component -D { "presets": [["es2015& ...
- 第十章 函数式接口&Stream流
10.1.函数式接口 10.1.1.概述 有且仅有一个抽象方法的接口,并且可以通过在类上标注@FunctionalInterface注解进行检测,建议自定义的函数式接口都加上这个注解 10.1.2.函 ...
- Django学习路27_HTML转义
谨慎使用 自动渲染语法 {{code|safe}} urls.py 中添加对应的函数 url(r'getcode',views.getcode) 在 views.py 中添加 def getcode( ...
- PHP array_intersect_uassoc() 函数
实例 比较两个数组的键名和键值(使用用户自定义函数比较键名),并返回交集: <?phpfunction myfunction($a,$b){if ($a===$b){return 0;}retu ...
- PHP preg_replace_callback_array() 函数
preg_replace_callback_array 函数执行一个正则表达式搜索并且使用一个回调进行替换.高佣联盟 www.cgewang.com 该函数在 PHP7+ 版本支持. 语法 mixed ...
- PHP strrchr() 函数
实例 搜索 "world" 在字符串中的位置,并返回从该位置到字符串结尾的所有字符: <?php高佣联盟 www.cgewang.comecho strrchr(" ...