返回完整目录

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); //编译期错误

模板的“编译”有两个阶段:

  1. 定义时(definition time)不进行实例化,模板代码的正确性将被检查(但不考虑模板参数),这包括

    • 发现语法错误,比如丢失分号等;

    • 发现使用不依赖于模板参数的未知名字(类型名、函数名...);

    • 检查不依赖于模板参数的静态断言(static assertion);

  2. 实例化期间(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章中进行讨论。此时,可以使用最简单的方法:在头文件中实现每一个模板

脚注


  1. 注意到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 呢?

  2. C++17以前,类型T必须是可拷贝的,才能进行参数传递给函数。但是自从C++17,可以传递临时量(temporaries)(右值,见附录B)尽管没有有效的拷贝构造和移动构造。

  3. 比如说,如果一种参数类型在std命名空间中被定义(如std::string),根据C++查找规则,全局的和std命名空间中的max()都可以被发现(参见附录C)。

  4. “一个实体适用于所有类型”的方案是可信的,但是实践中没有被采用(这将导致运行时效率降低)。所有的语言规则都是基于一个原则:不同的模板参数生成不同的实体。

  5. 在面向对象编程中,术语实例和实例化也被使用,指一个类的实例(concrete object of a class)。然而由于此书关于模板,该术语指使用模板的语境,除非特殊说明。

  6. 比如说,一些版本的Visual C++的编译器(如Visual Studio 2013 和 2015)允许不依赖于模板参数的未声明的名字,甚至允许一些语法缺陷(syntax flaws),比如丢失分号。

C++ Templates (1.1 初窥函数模板 A First Look at Function Templates)的更多相关文章

  1. C++ Templates (1.5 重载函数模板 Overloading Function Templates)

    返回完整目录 目录 1.5 重载函数模板 Overloading Function Templates 1.5 重载函数模板 Overloading Function Templates 和普通函数一 ...

  2. Scrapy001-框架初窥

    Scrapy001-框架初窥 @(Spider)[POSTS] 1.Scrapy简介 Scrapy是一个应用于抓取.提取.处理.存储等网站数据的框架(类似Django). 应用: 数据挖掘 信息处理 ...

  3. 初窥Flask

    初窥Flask Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求 ...

  4. 初窥Kaggle竞赛

    初窥Kaggle竞赛 原文地址: https://www.dataquest.io/mission/74/getting-started-with-kaggle 1: Kaggle竞赛 我们接下来将要 ...

  5. scrapy2_初窥Scrapy

    递归知识:oop,xpath,jsp,items,pipline等专业网络知识,初级水平并不是很scrapy,可以从简单模块自己写. 初窥Scrapy Scrapy是一个为了爬取网站数据,提取结构性数 ...

  6. Effective C++ -----条款45:运用成员函数模板接受所有兼容类型

    请使用member function templates(成员函数模板)生成”可接受所有兼容类型“的函数. 如果你声明member templates 用于“泛化copy构造”或“泛化assignme ...

  7. 读书笔记_Effective_C++_条款四十五:运用成员函数模板接受所有兼容类型

    比如有一个Base类和一个Derived类,像下面这样: class BaseClass {…}; class DerivedClass : public BaseClass {…}; 因为是父类与子 ...

  8. 网页3D效果库Three.js初窥

    网页3D效果库Three.js初窥 背景 一直想研究下web页面的3D效果,最后选择了一个比较的成熟的框架Three.js下手 ThreeJs官网 ThreeJs-github; 接下来我会陆续翻译 ...

  9. iOS视频直播初窥:高仿<喵播APP>

    视频直播初窥 视频直播,可以分为 采集,前处理,编码,传输, 服务器处理,解码,渲染 采集: iOS系统因为软硬件种类不多, 硬件适配性比较好, 所以比较简单. 而Android端市面上机型众多, 要 ...

随机推荐

  1. Repeating Decimals UVA - 202---求循环部分

    原题链接:https://vjudge.net/problem/UVA-202 题意:求一个数除以一个数商,如果有重复的数字(循环小数),输出,如果没有,输出前50位. 题解:这个题一开始考虑的是一个 ...

  2. dos格式迭代转为unix

    #!/bin/bash function recurse_convert() { local path=$ if [ "$path" == "" ];then ...

  3. python的__get__方法看这一篇就足够了

    get类型函数 直接上代码: class TestMain: def __init__(self): print('TestMain:__init__') self.a = 1 if __name__ ...

  4. 《HelloGitHub》第 52 期

    兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程.对开源社区感兴趣 人群的月刊,月刊的内容包括:各种编 ...

  5. Python三引号

    Python三引号:多用作注释.数据库语句.编写 HTML 文本. strs = ''' 使用了三引号的字符串 ''' print (strs) # 在 ‘’‘ 里可以使用转义字符 strs = '' ...

  6. Django学习路28_ .html 文件继承及<block 标签>,include 'xxx.html'

    在 templates 文件夹下创建 基类 base.html <!DOCTYPE html> <html lang="en"> <head> ...

  7. Python 中 False 和 True 关键字

    False:布尔类型,假.当条件判断不成立时,返回False. # == 判断两个对象的值是否相等 print('' == False)# False print(None == False)# Fa ...

  8. PHP acos() 函数

    实例 返回不同数的反余弦: <?phpecho(acos(0.64) . "<br>");echo(acos(-0.4) . "<br>&q ...

  9. PHP crypt() 函数

    定义和用法 crypt() 函数返回使用 DES.Blowfish 或 MD5 算法加密的字符串.高佣联盟 www.cgewang.com 在不同的操作系统上,该函数的行为不同,某些操作系统支持一种以 ...

  10. Vue笔记(有点乱)

    Vue学习笔记(2019.7.31) 目录 Vue学习笔记(2019.7.31) vue 基本指令用法 v-cloak v-text v-html v-bind v-on 跑马灯 v-on v-mod ...