c++11—引用类型
万能引用
左值和右值
std::move&std::forward
之前也写过std::move&std::forward
相关的文章,也是写完后好久没看过了,最近学习模板的相关知识,发现以前的文章理解比较浅薄且存在问题,在此重新进行分析。
std::forward
表示完美转发,能将万能引用的形参进行完美转发。
直接上样例:
|
样例中给出了std::forward
的可能实现Forward
,其中一个重载的参数类型是左值引用,另一个重载的参数类型是右值引用。这意味着传入的左值会调用左值引用的重载,传入的右值会调用右值引用的重载。以左值引用的重载进行分析。
template<class T> |
std::remove_reference_t<T>
表示去除类型T
中的引用类型。如果T
是一个左值引用类型U&
,通过引用折叠的规则,函数的返回类型T&&
会变成左值引用U&
,返回值变成static_cast<U&>(arg)
,整个函数模板实例化后,可以看成下面这种形式,此时的arg
通过Forward
处理后,输出的仍然是一个左值arg
。
template<class U> |
如果T
是一个右值引用类型U&&
,整个函数模板实例化后,可以看成下面这种形式,此时的arg
通过Forward
处理后,输出的仍然是一个右值arg
。
template<class U> |
我们再看样例中的函数模板print
:
template<class T> |
其中的T&& t
是一个万能引用。
万能引用:如果一个变量或者参数被声明为T&&
,其中T是可以被推导的类型,那这个变量或者参数就是一个万能引用。如果用来初始化万能引用的表达式是一个左值,那么万能引用就变成了左值引用,如果用来初始化万能引用的表达式是一个右值,那么万能引用就变成了右值引用。
当传入的实参是左值,T
会被推导为U&
,引用折叠后的t
是左值引用类型。当传入的实参是右值,T
会被推导为U&&
,引用折叠后的t
是右值引用类型。当传入的实参是引用类型(左值引用或右值引用),函数模板会忽略其引用类型,例如传入的实参如果是int& a
,相当于传入int a
。
现在考虑完整的样例,函数模板print
中实现为:
template<class T> |
输出结果为:
test &
test &
这里调用两次print
,一次传入的是左值,一次传入的是右值,当传入左值,我们希望内部调用左值引用的test
,当传入右值,我们希望调用的是右值引用的test
,但实际上调用的都是左值引用的test
。我们来看下传入右值的情况,当右值传入到print
,此时的形参t
虽然是右值引用类型,但此时的变量t
是一个左值,所以调用的也只能是左值引用的test
。我们想传入test
的t
保持和传入print
的实参具有同样的值性,即左值仍然是左值,右值仍然是右值。这个时候就需要用到完美转发,现在函数模板print
的实现为:
template<class T> |
输出结果为:
&
test &
&
test &&
利用Forward
我们得到了想要的结果,我们再对传入右值进行分析。此时t
仍是一个左值,调用的是左值引用的Forward
,此时的T
也会被推导为U&&
,即我们会调用Forward<U&&>(U&)
,此时会返回一个右值,即传入test
的是一个右值,调用的也就是右值引用的test
。
std::move
我们直接看一个实例:
|
实例中的Move
是std::move
的可能实现。T&& arg
是一个万能引用,std::remove_reference_t<T>
能去除T
的引用类型,返回值是将arg
强转成了右值引用类型,返回类型也是一个右值引用,通过Move
我们能得到一个右值。
上述样例的输出为:
test &
test &&
变量a
原本是一个左值,Move(a)
返回了一个右值,符合我们的预期。为啥需要std::move
,因为我们需要将一个左值转换成右值,为啥需要右值,因为我们需要支持移动语义。