Item 24: Distinguish universal references from rvalue references
为了声明指向某类型T的右值引用,你会写T&&。这会使我们在看到源码种出现“T&&”时,就认为是一个右值引用。但,没那么简单:
void f(Widget&& param); // rvalue referenceWidget&& var1 = Widget(); // rvalue referenceauto&& var2 = var1; // not rvalue referencetemplate<typename T>
void f(std::vector<T>&& param); // rvalue referencetemplate<typename T>
void f(T&& param); // not rvalue reference
事实上,“T&&” 有两种不同的涵义。当然,其中一个是右值引用的意思。这种引用行为就是你所期望的:它们只绑定到右值上去,并且它们的主要职责就是去明确一个对象是可以被move的。
“T&&” 的另一种含义是,则表示其既可以是右值引用,也可以是左值引用。带有这种含义的引用在代码中形如右值引用(即 “T&&”),但它们能表现的像左值引用一样(即 “T&”)。这种双重特性使之既可以绑定到右值(如右值引用),也可以绑定到左值(左值引用)。此外,它们也可以绑定到const对象或非const对象,以及volatile对象或非volatile对象,甚至绑定到既带有const又带有volatile属性的对象。它们几乎可以绑定到任何东西。我们称这种史无前例的灵活性的引用为万能引用【Item 25将会解释万能应用几乎总是需要应用std::forward。目前C++ 委员会的一些成员开始称其为转发引用(forwarding references)】。
万能引用出现在两种场景下。最常用的场景是函数模板参数,比如上面的代码:
template<typename T>
void f(T&& param); // param is a universal reference
另外一种场景是auto声明,比如上面代码中的:
auto&& var2 = var1; // var2 is a universal reference
这两种场景的共同之处,在于它们都涉及到类型推导。在模板f中,param的类型是推导得到的,而在var2的声明语句中,var2的类型也是推导得到的。相比之下,下面的例子就不涉及到类型推导。如果你看到没有类型推导的 “T&&” 时,那么就是右值引用:
void f(Widget&& param); // no type deduction;// param is an rvalue referenceWidget&& var1 = Widget(); // no type deduction;// var1 is an rvalue reference
因为万能引用也是引用,所以它必须被初始化。万能应用的初始化物(initializer)决定了它代表的是左值引用还是右值引用。如果initializer是右值,万能引用相当于右值引用,如果initializer是左值,则万能应用相当于左值引用。对于作为函数形参的万能引用而言,初始化物(initializer)在调用出提供:
template<typename T>
void f(T&& param); // param is a universal referenceWidget w;
f(w); // lvalue passed to f; param's type is// Widget& (i.e., an lvalue reference)f(std::move(w)); // rvalue passed to f; param's type is// Widget&& (i.e., an rvalue reference)
要使一个引用成为万能引用,类型推导是必要非充分条件。引用声明的形式也必须正确无误,且该形式被限定的很死:必须形如 “T&&” 才行【换一种好理解的表述:如果函数模板形参具备T&&格式,且T类型是推导而来,或者对象使用auto&&声明其类型,则该形参或对象就是个万能引用】。再看一次这个我们之前在示例代码中看过的例子:
template<typename T>
void f(std::vector<T>&& param); // param is an rvalue reference
当f被调用时,类型T将被推导(除非调用者显示指明了类型,这是一种我们不必关心的边界情况),但paramr的类型声明形式不是 “T&&”,而是 “std::vector&&”。这就排除了param是万能引用的可能性。因此,param是一个右值引用,如果你尝试向f传递左值,编译器会很乐意为你确认出来:
std::vector<int> v;
f(v); // error! can't bind lvalue to// rvalue reference
即使是一个const修饰的存在,也足以褫夺一个引用成为万能引用的资格:
template<typename T>
void f(const T&& param); // param is an rvalue reference
如果在模板内看到一个函数的形参类型写作 “T&&”,你可能想当然的认为它肯定是一个哇能引用。其实不然。因为位于模板内并不保证一定涉及类型推导,考虑如下代码:
template<class T, class Allocator = allocator<T>> // from C++
class vector { // Standards
public:void push_back(T&& x);…
};
push_back的形参格式虽然符合万能引用的格式,但是并不涉及类型推导。因为push_back不能存在于vector的特定实例之外,并且实例的类型就完全能决定push_back的声明类型了。也就是说:
std::vector<Widget> v;
使得std::vector模板被实例化为下面这样:
class vector<Widget, allocator<Widget>> {public:void push_back(Widget&& x); // rvalue reference…
};
现在你能清楚地发现push_back没有用到类型推导。所以这个push_back是指向T的右值引用。
std::vector中还有一个与push_back概念类似的emplace_back就涉及到了类型推导:
template<class T, class Allocator = allocator<T>> // still from
class vector { // C++
public: // Standardstemplate <class... Args>void emplace_back(Args&&... args);…
};
在这里,类型参数Args独立于vector的类型参数T,所以每次emplace_back被调用的时候,Args必须被推导。(好吧,Args事实上是一个参数包,不是一个类型参数,但是为了讨论的目的,我们能把它视为一个类型参数。)
前面提到过aoto变量也可以作为万能引用。准确地说,声明为auto&&类型的变量都是万能引用,因为它们既涉及到类型推导,也有正确的格式(“T&&”)。 auto万能引用在C++ 11中没有像函数模板形参的万能引用那么常见,但是在C++ 14中却经常出现,尤其是写一个lambda表达式:
auto timeFuncInvocation =[](auto&& func, auto&&... params) // C++14{// start timer;std::forward<decltype(func)>(func)( // invoke funcstd::forward<decltype(params)>(params)... // on params);// stop timer and record elapsed time;};
也许你会对“std::forward<decltype(blah blah blah)>”这种形式的代码迷惑,不过没关系,Item 33中会有详细展示。
其实呢,本条款所说的万能引用是一个抽象的说法,它底层的真相被称为“引用折叠”。Item 28会专门讲这个问题。之所以本条款讨论了万能引用和右值引用的区别,目的是为了我们能更精准的阅读代码(“我看到的T&&只能绑定到右值上,还是能绑定到所有东西上呢?”),并且在你和同事讨论的时候,它能让你避免歧义。(“我在这里使用一个universal引用,不是一个右值引用…”)。它也能让你搞懂Item 25和Item 26的意思,这两个Item都依赖于这两个引用的区别。另外,掌握万能引用的概念会比了解引用折叠的技术细节是个更好的选择。
Things to Remember
- 如果函数模板形参具备T&&格式,并且T类型是推导而来,或者对象使用auto&&声明其类型,则该形参或对象就是个万能引用;
- 如果类型声明并不精确的匹配type&&格式,类型推导没有发生,type&&就代表右值引用;
- 如果采用右值初始化万能引用,就会得到一个右值引用;如果用左值初始化万能引用,就会得到一个左值引用;
Item 24: Distinguish universal references from rvalue references相关推荐
- C++11中rvalue references的使用
Rvalue references are a feature of C++ that was added with the C++11 standard. The syntax of an rval ...
- Rvalue References
Rvalue References
- item 24: 区分右值引用和universal引用
本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 古人曾说事情的真相会让你觉得很自在,但是在适当的情 ...
- Service References和Web References的引用
引用WebService方式有两种 1.Service References 添加服务引用 -------->输入webservice地址点击转到会出现命名空间对应的服务(可以进行修改命名空间) ...
- 《Effective Morden C++》Item 7: Distinguish between () and {} when creating objects
引子 从本Item开始,我们就进入了第二个章节,本章节,S.M.通过对比C++的新老feature来鼓励大家使用new features. 本Item先从对象初始化开始. 正文 1.基础 C++11中 ...
- 深入理解java中的Soft references amp;amp; Weak references amp;amp; Phantom reference
引言 Ethan Nicholas 在他的一篇文章中说:他面试了20多个Java高级工程师,他们每个人都至少有5年的Java从业经验,当他问这些工程师对于Weak References 的理解时,只有 ...
- JVM 内存分析神器 MAT: Incoming Vs Outgoing References 你真的了解吗?
点击上方蓝色字体,选择"设为星标" 优质文章,及时送达 了解 Eclipse MAT 中 incoming and outgoing 引用之间的区别. Eclipse MAT(内存 ...
- 使用 .NET 平台,如何玩转 Universal Windows 应用?
2015年7月30日 本文作者是 Managed Languages 团队项目经理 Lucian Wischik. 不久前,Visual Studio 2015上新增 Windows 10 应用的开发 ...
- 使用 .NET 平台,如何玩转 Universal Windows 应用? 1
2015年7月30日 本文作者是 Managed Languages 团队项目经理 Lucian Wischik. 不久前,Visual Studio 2015上新增 Windows 10 应用的开发 ...
最新文章
- rinetd 做端口转发
- python if调用函数,Python根据字符串调用函数过程解析
- CSDN:解决粉丝网友集中问题留言处,把你所有的问题在留言处留言,我会一一回答
- flashfxp连mysql_FlashFXP 命令行参数
- 谭浩强课后题(数组篇)
- ubuntu、fedora系统的启动|关闭管理器
- java线程6种状态转换,Java线程的生命周期和各种状态转换详解
- 3.3.4.5. 日期计算
- 阿里java工具包_阿里开源的Java诊断工具Arthas(阿尔萨斯)
- Proxy server got bad address from remote server
- [jQuery案例练习]——锅打灰太狼
- 数据的力量 |《2021—2022中国大数据行业发展报告》发布
- Excel怎么批量设置图片大小
- ubuntu命令行模式与图形桌面切换方法
- CSS 基础教程:CSS 教程:什么是 CSS?
- 用Hive、Impala查询Hbase数据
- 字符串操作函数strstr
- 短信验证码内容组成及设计注意事项
- 多态 在游戏程序实例
- 如何利用Win7Aero特效来美化你的程序窗口