C++11 图说VS2013下的引用叠加规则和模板参数类型推导规则
背景:
最近在学习C++STL,出于偶然,在C++Reference上看到了vector下的emplace_back函数,不想由此引发了一系列的“探索”,于是就有了现在这篇博文。
前言:
右值引用无疑是C++11新特性中一颗耀眼的明珠,在此基础上实现了移动语义和完美转发,三者构成了令很多C++开发者拍案叫绝的“铁三角”(当然不是所有C++开发者)。而在这个“铁三角”中,有一个无法回避的关键细节,那就是引用叠加规则和模板参数类型推导规则。其实,关于这两个规则,可查到的资料不少,但都有一个特点——简单(就形式而言)而难懂(就理解而言)(起码在下这么认为),而且,都没有例证,仅仅是简明扼要地交代。而本文恰恰是将这一细节展开,给出演示和证明。诚然,这不是什么开创性的工作,但在下认为也是必不可少的,因为它让人们对这一关键细节了解得更加深入和透彻,另外,从某个角度来说,也填补了空白。
“图说”是因为:有图有真相,一目了然,真真切切,不容辩驳。
“VS2013下”是因为:本文所有测试和截图都来自VS2013,考虑到不同编译环境下结果可能会略有不同,所以,严谨起见,这里加了“VS2013下”。
最后,再说两点:
1.本文的行文形式(也可以说是逻辑顺序):先结论,再证明,必要时加以解说。
2.本文使用了大量的截图,所以读起来可能会有一种连篇累牍之感(但实际上文章逻辑结构清晰,内容一目了然),给读者带来的阅读上的不适,敬请谅解。
参考资料:
1.维基百科.右值引用 地址:http://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8(强烈建议大家看)
2.聚客频道.[C++] 右值引用:移动语义与完美转发 作者:Dutor 地址:http://ju.outofmemory.cn/entry/105978
3.博客园.【原】C++ 11完美转发 作者:Hujian 地址:http://www.cnblogs.com/hujian/archive/2012/02/17/2355207.html
4.IBM developerWorks.C++11 标准新特性: 右值引用与转移语义 作者:李胜利 地址:http://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/
正文:
好了,书归正文。
为把问题说清楚,我们先给出以下函数:
template<typename T>
void f(T&& fpar)//formal parameter 形参
{
//函数体
}
//调用
int a=;
int& apar=a;//actual parameter 实参
f(apar);
在此基础上,给出以下表格(设A为基本类型,比如int):
表1
说明:
1.在前面的代码中,调用前形参fpar被声明的类型是T&&,调用时传入的实参apar的类型是int&。
2.上表中,2、3、4列对应了引用叠加规则,2、3、5列对应了模板参数类型推导规则。
3.由上表可以知道:
引用叠加规则的规律是:调用前fpar与apar中有一个是&,结果(即调用后fpar的实际类型)就是&;只有当fpar与apar都是&&时,结果才是&&。
模板参数类型推导规则的规律是:只有调用前fpar是&&,apar是&时,调用后T的实际类型才是A&,其余3种情况下都是A。(仅就上表,许多资料上不上这样,原因
在于红色部分不一样,见下面的说明4)
4.注意到上表中红色的A,在查阅过的资料中,那个位置是A&,但在下得到的结果却是A,后面会详细解释。
5.本文所讨论的模板参数类型推导,仅是针对上面例子中的T而言的,在C++11里,更经典的类型推导包括auto,decltype等。
下面逐一给出验证与说明:
1.验证规则1
看图:
图1
程序中我们设断点监视变量,我们看到,ra作为int&类实参调用函数wai(因为是外层函数,这里简单命名为wai,不影响说明问题),调用后,T& w_a变成了int& w_a(即实际类型成了int&),而T w_aa成了int型,即T的类型是int型。这里,调用后形参w_a的实际类型满足引用叠加规则1(上表中的)。
关于引用叠加,有两种理解方式(以上例为例说明):
方式一:
参数传递时,T&与int&“作用”,结果是int&,即T&+int& -> int&。我们将其视为规定,不必解释。(上表正是以这种方式给出的)
方式二:
参数传递时,将实参ra前面的int&传给T(即将T换成int&),于是,int& & -> int&(注意int& &的两个‘&’间有空格,不是右值引用),而将int& & ->
int&视为规则。基于方式二,上表将变成(不考虑调用后T的类型):
表2
其中,第1个”加数“是将T换成的内容,也就是实参前的类型,第2个”加数“是函数参数列表中T后的引用形式,”和“是函数调用后形参的实际形式。下面图说方式二中规定的正确性:
A& & -> A& A& && -> A& A&& & -> A& A&& && -> A&&
两种方式都可以。只不过在下觉得,方式二绕一点,并且,有一种T先变成int&(以图1所示为例),然后又变成int的莫名其妙之感。所以,在下推荐方式一。
在T的推导上,我们采用这样的方式:先由叠加原理得出函数调用后形参的类型,然后将该类型与函数参数列表中形参的类型进行对比、匹配,从而得出T的类型。
如果发现不能匹配,则再次运用叠加规则”推导“出T的类型(我们将在验证规则3时遇到这种情况)。
以图1中的情况为例:
T& w_a (形参列表中的)
int& w_a (函数调用后形参的实际类型,由叠加规则决定)
对比知,T为int型。
2.验证规则2
图说:
图2
这似乎已经验证了规则2,但请看下图:
图3
不知是否有人会惊讶,a明明是右值引用,为什么会调用void f(int& lfa)?换句话说,a什么时候变成了左值?
现在,要告诉大家一个结论(相信许多人都知道,就当在下是重复吧):
C++标准规定,具名的右值引用被当作左值。[注 6]这一规定的意义在于,右值引用本来是用于实现移动语义,因而需要绑定一个对象的内存地址,然后具有修改这一对象内容的权限,这些操作与左值绑定完全一样。右值绑定与左值绑定的分野在于确定函数重载时的分辨。对于移动构造成员函数与移动赋值运算符成员函数,其形、实参数结合时是按照右值引用处理;而在这两个成员函数体内部,由于形参都是具名的,因而都被当作左值,这就可以用该形参来修改传入对象的内部状态。另外,右值引用作为xvalue(临终值)本来是用于移动语义中一次性搬空其内容。具名使其具有更为持久的生存期,这是危险的,因而规定具名后为左值引用,除非程序显式指定其类型强制转换为右值引用。
——维基百科 地址:http://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8
另外,从上图也可以看出,&&和&的不同可以作为重载标志。
现在,相信大家也不再惊讶。回过头来看图2,我们明白,这个验证是无效的,ra被当成左值,相当于还是在验证规则1。那么,怎么办呢?看下图:
图4
虽然结论没有变化,但这种验证方法是有效的。
读者可以在图4代码的基础上,加入图3中的两个f函数,然后在main函数中写f(rt());会得到“右值:1”这样的输出。为缩短文章篇幅,这里就不截图了,请读者自己验证。
关于图4的代码,说以下几点:
1.前面说过,具名右值引用按左值引用处理,所以,要达到实验目的,不能将具名右值引用传给函数wai(),所以我们传函数返回值这样的不具名右值引用。
2.如果我们返回局部变量或是临时对象的引用(比如在rt()函数中写int a=1;return a++;,哪怕将int a=1;放在全局,也是不行的,因为a++就是返回++前a的一份拷贝,属于临时对象),结果是不正确的(得不到输出1)。(具体原因在下暂时还不清楚,可能是后边的代码执行时将临时变量的空间覆盖(重写)了,在下反汇编单步也没找出确切的答案(在下汇编学得不怎么样),这里烦请有知道原因的大牛给出指点,在下感激不尽,先行谢过)
3.就像大家在图4中看到的那样,rt()函数中必须将全局变量a强制类型转换为int&&型再返回,否则,如果写成return a;,编译器将产生类似“无法将右值引用绑定到左值”的报错,原因是具名右值引用a被当做左值。
4.void wai(const T& w_a)中的const不能省,原因是非常量引用(T&)不能接受右值引用。
5.void nei(const int& n_a)中的const也不能省,正如大家在图4中看到的,在wai()中执行nei(w_a);时,w_a为const int&类型。
简单说一下T的推导:
const T& w_a (参数列表中)
const int& w_a (函数调用后w_a的实际类型)
对比知,T为int型。
至此,我们可以确定,表1中红色的A是正确的,A&的说法有误。
3.验证规则3
图说:
这里只说一下T的推导。如下:
T&& w_a (参数列表中w_a的类型)
int& w_a (函数调用后w_a的实际类型)
显然,此时无法直接匹配。这里我们运用表2(之所以用表2,是因为表2比表1更加直观)中的第2条A& + && -> A&,推出T为int&类型。
4.验证规则4
图说:
这里首先说一点,前边我们说过,非常量左值引用不能接受右值引用,上图中,void nei(int& n_a),w_a为int&&类型,那么,rt()中的nei(w_a);是如何通过的呢?
不要忘了,虽然w_a显示为int&&类型,但它是具名右值引用,所以作为左值引用处理,自然能够通过。如果我们将void nei(int& n_a)改为void nei(int&& n_a),反而不能通过(w_a被当做int&型,int&&不能接受int&),读者可以自己试一试。
再说一下T的推导:
T&& w_a (参数列表中w_a的类型)
int&& w_a (函数调用后w_a的实际类型,不考虑C++11将其视为int&)
对比,知T为int型。
至此,4个引用叠加规则和相应的模板参数类型推导都说完了,谢谢大家!
后记:
在下爱钻研,喜探究,实事求是;但另一方面,又着实才疏学浅,能力有限,所以只能做一些基础性的工作。但即便如此,也难免有疏漏乃至错误之处,这里,在
下恳请大家批评指正,不吝赐教。您的批评指正就是在下不断进步的源泉!
C++11 图说VS2013下的引用叠加规则和模板参数类型推导规则的更多相关文章
- C++11 引用叠加规则和模板参数类型推导规则
http://zm8.sm-img2.com/?src=http%3A%2F%2F***%2FArticle%2F38320&uid=57422b713ac761e653af7b327bfd9 ...
- 图说函数模板右值引用参数(T&&)类型推导规则(C++11)
见下图: 规律总结: 只要我们传递一个基本类型是A④的左值,那么,传递后,T的类型就是A&,形参在函数体中的类型就是A&. 只要我们传递一个基本类型是A的右值,那么,传递后,T的类型就 ...
- [转载] C++11中的右值引用
C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导 ...
- C++11中的右值引用
原文出处:http://kuring.me/post/cpp11_right_reference May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移 ...
- C++11标准之右值引用(rvalue reference)
1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了Copy Elision.RVO(包 ...
- 11、mybatis的映射xml中参数类型的别名
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型.需要resultType指定输出结果的映射类型. 如果在指定类型时输入类型全 ...
- 关于VS2013下制作和使用静态库和动态库
关于VS2013下制作和使用静态库和动态库 引言 什么是库:库是写好的现有的,成熟的,可以复用的代码. 所谓静态.动态是指链接.将一个程序编译成可执行程序的步骤: 静态库在链接阶段,会将汇编生成的目标 ...
- C++ 11 中的右值引用
C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream> #include &l ...
- 在win 7 vs2013下 web 调试 出现“ iis Express Worker Process 已停止工作”错误
在win 7 vs2013下 web 调试 出现“ iis Express Worker Process 已停止工作”错误: 如下图: 最终解决方案如下: 用管理员身份运行CMD,输入netsh ...
随机推荐
- 使用safe-rm替代rm
safe-rm主页: https://launchpad.net/safe-rm 一.下载safe-rm 二.解压文件,拷贝safe-rm到/usr/local/bin/目录下 三.建立软连接: #l ...
- mysql5.5 修改字符集
对于使用者来说,一般推荐使用utf8编码来存储数据.而要解决乱码问题,不单单是MySQL数据的存储问题,还和用户的程序文件的编码方式.用户程序和MySQL数据库的连接方式都有关系. 首先,MySQL有 ...
- 几个常见Win32 API函数
1.获取客户区矩形区域 RECT cliRect; GetClientRect(hWnd, &cliRect); 2.获取窗口上下文句柄 HDC hdc = GetDC(hWnd);//... ...
- yiii 框架登录 判断是否是游客模式及未登录状态
原地址:http://blog.csdn.net/a553181867/article/details/50987388 最近在利用Yii 2.0框架进行项目后台的编写,遇到的第一个问题是用户登陆,包 ...
- angular 路由去除#号
1. 路由启动 $locationProvider.html5Mode(true); 通过pushstatex修改url app.js define([ 'angular', & ...
- Velocity模板引擎语法
Velocity 模板引擎介绍 Velocity是一个基于java的模板引擎(template engine).它允许任何人仅仅简单的使用模板语言(template language)来引用由java ...
- POJ 2891 Strange Way to Express Integers(拓展欧几里得)
Description Elina is reading a book written by Rujia Liu, which introduces a strange way to express ...
- Swift数据类型简介(二)
整数 整数就是没有小数部分的数字,比如42和-23.整数可以是有符号(正.负.零)或者无符号(正.零). Swift 提供了8,16,32和64位的有符号和无符号整数类型.这些整数类型和 C 语言的命 ...
- [Effective JavaScript 笔记]第7章:并发--个人总结
前言 这一章的内容学到了事件队列和异步的API.js只是运行在其他应用程序的脚本语言.js即依赖于应用程序,也独立与应用程序.可以使它可以在多平台,多种环境上运行.ECMAScript标准中没有关于并 ...
- 看门外汉如何实现:C#操作 MongoDB基本CURD的事务控制
第一部分 基本设计 目前最新版本的C#驱动MongoDB-CSharpDriver-2.2.3,比之前的版本更新比较大,在网上很难找到这个版本的相关C#操作资料,以下都是个人自发研究.测试的,如有雷同 ...