返回完整目录

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. 没想到 Hash 冲突还能这么玩,你的服务中招了吗?

    背景 其实这个问题我之前也看到过,刚好在前几天,洪教授在某个群里分享的一个<一些有意思的攻击手段.pdf>,我觉得这个话题还是有不少人不清楚的,今天我就准备来“实战”一把,还请各位看官轻拍 ...

  2. sublimeCLang配置报错以及sublime快捷键

    subimeClang需要手动配置我真的真的很服 记录一下满是报错的高光时刻 -------- 不过这个问题刚刚解决了 只要把所有的shared_ptr改成std::shared_ptr 就行 说白了 ...

  3. Blash数组 c++

    //输入一个数作为Blash数组的根, //对于该数组的每一个数x,x*2+1 x*3+1均在该数组 //并且该数组没有其他数字 //该数组升序排列 //输入a,n 输出该数组第n个数 // // # ...

  4. checkbox变成单选型

    checkbox的特性是可以选中或者取消,有时需要利用这一点做一个类似radio的选项框: <input type="checkbox" class="aa&quo ...

  5. PHP array_intersect_key() 函数

    实例 比较两个数组的键名,并返回交集: <?php$a1=array("a"=>"red","b"=>"gree ...

  6. PHP count_chars() 函数

    实例 返回一个字符串,包含所有在 "Hello World!" 中使用过的不同字符(模式 3): <?php高佣联盟 www.cgewang.com$str = " ...

  7. Skill art函数遍历字典

    https://www.cnblogs.com/yeungchie/ code procedure(ycartGo(length1) prog(() for(x 1 length1 printf(&q ...

  8. MySQL中EXPLAIN命令详细解析

    很多情况下我们需要知道某条SQL语句的性能,都会通过EXPLAIN命令来查看查询优化器是如何执行的. 如何使用 使用EXPLAIN很简单,只需要在执行的SQL前面加上EXPLAIN即可 explain ...

  9. JQuery插件,轻量级表单模型验证(续 二)

    好不容易,有心思,那就把没做完的JQuery轻量级表单验证做完吧 之前做到了空参数验证的,现在增加带参数的验证. 附上html <form id="ValidataForm" ...

  10. 32-关键字:abstract

    abstract: 抽象的 1.可以用来修饰:类.方法 2.具体的:abstract修饰类:抽象类 * > 此类不能实例化 * > 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对 ...