【翻译】VC10中的C++0x新特性:右值引用(rvalue references) (3)
2011-06-23 13:30
716 查看
零度の冰翻译,原文地址在此,转载请注明出处。 接【翻译】VC10中的C++0x新特性:右值引用(rvalue references) (2) 右值引用:模板参数推导和引用折叠右值引用和模板以一种特别的方式相互作用。下面是一个示例:
当我们调用quark(up),编译器先进行模板类型参数的推导。quark是一个函数模板,其模板参数是T,但是我们调用它时并没有显式的给出类型参数(像quark<X>(up)),而是通过比较函数的形参Type&&和实参(一个string类型的左值)来推导出模板的类型参数。 C++0x 会转换函数实参的类型和形参的类型,然后再把它们匹配在一起。 首先,编译器转换函数实参类型。一条特殊的规则(N2798 14.8.2.1 [temp.deduct.call]/3)被激活了:当一个函数形参类型是T&&(T是一个模板参数),函数实参又是一个A类型的左值,那么使用A&来进行模板参数推导。(这条特殊的规则不适用于函数实参为T&或是const T&的情况,它们会按照C++98/03规则推导。此特殊规则也不适用于const T&&。)在quark(up)这种情况,适用特殊规则,实参类型被转换为string&。 接着,编译器转换函数形参类型,C++98/03和C++0x都会忽略引用(C++0x即忽略左值引用,又忽略右值引用),在所有四次调用时,都意味着我们将T&&转换成为了T。 因此,我们推导出T为函数实参类型。这就是为啥quark(up)输出了“T::string&”,quark(down)输出了“T::const string&”,up和down都是左值,因此它们激活了那条特殊规则。strange()和charm()是右值,因此它们使用正常的规则,这就是为啥quark(strange())输出“T::string”,quark(charm())输出“T::const string”了。 模板参数推导之后,编译器开始替换操作。编译器把每个出现的T都替换成推导出的类型,在quark(strange())中,T是string,因此T&&是string&&。类似的,在quark(charm())中,T是const string,因此T&&是const string&&。但是,quark(up)和quark(down)激活了另一条特殊规则。 在quark(up)中,T是string&,T&&的替换操作导致了结果string& &&。在C++0x中引用的引用将会退化,并且引用退化的规则是“左值的引用具有传染性”,X& &,X& &&,和X&& &,都退化为X&,只有X&& &&退化为了X&&。因此,string& &&退化到string&。在模板函数中,那些看起来像右值引用的参数,其实并不一定是。quark(up)实例化为了quark<string&>()。在这个实例中,参数T&&变为了string&。我们通过Name<T&&>::get()已经观察到了这一点。类似的,quark(down)实例化为了quark<const string&>(),参数T&&变为了const string&。在C++98/03中,你可能已经习惯了常量性隐藏于模板参数中了(一个接受参数T&的函数模板可以使用const Foo对象去调用;使T&看起来变为了const Foo&),在C++0x中,左值性也可以隐藏在函数模板参数中。 好的,那么我们要问这两条特殊规则给我们带来了什么?在quark()中,T&&有着和实参同样的左右值和常量属性,因此可以用右值引用这种保留实参左右值和常量性的特点实现完美转发。 完美转发:std::forward()和std::identity如何工作让我们再次看一下outer():
幸运的是,匿名的左值引用是左值,而匿名的右值引用是右值。因此,为了将t1和t2转发给inner(),我们需要使用辅助函数保存它们的类型信息但是移除它们的名字。这就是std::forward()的功能:
你可能会问如果把Forward<T1>(t1)写成Forward<T1&&>(t1)的话会发生什么(这是个经常出现的错误,因为outer的参数就是T1&&)。幸运的是,这样不会导致什么坏的结果。因为Forward<T1&&>()将接受和返回T1&& &&类型,它会退化成T1&&。因此Forward<T1>(t1)和Forward<T1&&>(t1)是一样的,但是前面的形式更短,因此更加流行。 Identity干了什么?为什么下面的形式无法工作呢?
move语义:std::move()是如何工作的现在我们已经了解了模版类型推导中的特殊规则,以及引用退化,让我们再看一下std::move():
同样的,Move()和C++0x头文件<utility>的实现机制一样。 当Move()被一个左值string调用,T被推断为string&,因此Move()接受到string&类型的参数(引用退化过以后),经过RemoveReference,返回值为string&&。 当Move()被一个左值const string调用,T被推断为const string&,因此Move()接受到const string&类型的参数(引用退化过以后),经过RemoveReference,返回值为const string&&。 当Move()被一个右值string调用,T被推断为string,因此Move()接受到string&&类型的参数,经过RemoveReference,返回值为string&&。 当Move()被一个右值const string调用,T被推断为const string,因此Move()接受到const string&&类型的参数,经过RemoveReference,返回值为const string&&。 这就是std::move()保持类型的常量属性的同时,将左值转换为右值返回的原理。 回顾如果你想了解更多的右值引用的信息,你可以阅读它们的提案。要注意的是现在的情况可能已经跟提案不太一样了,右值引用已经被纳入了C++0x Working Paper,并得到了持续的改进。提案中有的部分已经过时了,有的不再正确,或者没有被C++0x标准采纳。但它仍然具有很大的参考价值。N1377,N1385,和N1690是右值引用主要的草案。N2118包含了草案的被纳入C++0x Working Paper之前的最终版本。N1784,N1821,N2377,和N2439记录了将move语义扩展到*this的进化过程,它已经成为了C++0x标准,但VC10尚未实现它。 展望N2812“右值引用的一个安全问题(以及如何解决它)”提出了对初始化规则的修改:禁止右值引用绑定到左值上。这不会影响到move语义和完美转发,所以不会导致你刚学到的新技术的失效(但是它会导致std::move()和std::forward()实现方式的改变)Stephan T. Lavavej Visual C++ Libraries Developer |
相关文章推荐
- 【翻译】VC10中的C++0x新特性:右值引用(rvalue references) (1)
- 【翻译】VC10中的C++0x新特性:右值引用(rvalue references) (2)
- 【译】VC10中的C++0x特性 Part 2 (1):右值引用
- VC10中的C++0x特性 Part 2 (1):右值引用
- VC10中的C++0x特性 Part 2 :右值引用
- 【译】VC10中的C++0x特性 Part 2 (2):右值引用
- 【译】VC10中的C++0x特性 Part 2 (2):右值引用
- 【译】VC10中的C++0x特性 Part 2 (1):右值引用
- [C++基础]020_C++0x新特性之右值引用(int&& value)
- [C++基础]020_C++0x新特性之右值引用(int&& value)
- 一篇好文:VC2010中的C++0x特性 Part 2:右值引用
- 【译】VC10中的C++0x特性 part 1:Lambdas,auto,以及 static_assert
- VC10中的C++0x特性 Part 2 (3):右值引用
- 【译】VC10中的C++0x特性 part 1:Lambdas,auto,以及 static_assert
- 【译】VC10中的C++0x特性 part 3 : 声明之类型
- 【译】VC10中的C++0x特性 part 3 : 声明之类型
- 探索C++0x: 3. 右值引用(rvalue reference)
- 探索C++0x: 3. 右值引用(rvalue reference)
- VC10中的C++0x特性:Lambdas,auto,以及 static_assert
- VC2010中的C++0x特性 Part 2:右值引用