C++左右值转换
1. 示例
template<typename T>
void print(T & t){
std::cout << "Lvalue ref" << std::endl;
}
template<typename T>
void print(T && t){
std::cout << "Rvalue ref" << std::endl;
}
template<typename T>
void testForward(T && v){
print(v);//v此时已经是个左值了,永远调用左值版本的print
print(std::forward<T>(v)); //本文的重点
print(std::move(v)); //永远调用右值版本的print
std::cout << "======================" << std::endl;
}
int main(int argc, char * argv[])
{
int x = 1;
testForward(x); //实参为左值
testForward(std::move(x)); //实参为右值
}
执行结果如下:
Lvalue ref
Lvalue ref
Rvalue ref
======================
Lvalue ref
Rvalue ref
Rvalue ref
======================
std::move: 功能将一个左值/右值, 转换为右值引用。 主要是将左值强制转为右值引用,因为右值引用无法直接绑定到左值上, 为了能让右值引用绑定到左值上, 必须将左值转为右值引用,std::move提供做的就是这个。 对于传入右值, 那么std::move将什么都不做, 直接返回对应的右值引用。
std::forward: 功能将参数类型原封不打转发到一下个函数, 包括const属性。 这就是所谓的**“完美转发(perfect forwarding)”**
2. 引用折叠
引用折叠前 | 引用折叠后 | |
---|---|---|
左值引用-左值引用 | T& & | T& |
左值引用-右值引用 | T& && | T& |
右值引用-左值引用 | T&& & | T& |
右值引用-右值引用 | T&& && | T&& |
由此可见, 只有一种情况折叠成右值引用, 即右值引用的右值引用。 其他都折叠为左值引用。
3. move
std::move实现如下
template<typename _Tp>
_GLIBCXX_NODISCARD
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
std::move函数类型为Tp_&&, 一个指向模板类型参数的右值引用, 通过引用折叠,此参数可以与任意类型匹配。
std::remove_reference<_Tp>的实现,可以看出,其主要是将去除类型的引用。
template<typename _Tp>
struct remove_reference
{ using type = _Tp; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ using type = _Tp; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ using type = _Tp; };
sting s1("hi!"), s2;
s2 = std::move(string("bye!")); // 传入一个右值
s2 = std::move(s1); // 传入一个左值
在s2 = std::move(string(“bye!”)); 传入的是一个右值。因此,在std::move模板函数中,
-
推断出的T类型为string
-
因此, std::remove_reference用string进行实例化
-
std::remove_reference的type成员是string
-
move的返回类型是string&&
-
move的参数__t类型为string&&.
因此std::move最终被实例化如下。 __t类型已经是string&&, 因此类型转换什么都不用做。 即对于传入右值的std::move函数, 实际上move函数什么都不用做。
在s2 = std::move(s1); 传入的是一个左值,因此, 在std::move模板函数中,
-
推断出T的类型为string&
-
因此,std::remove_reference用string& 进行实例化
-
std::remove_reference的type成员是string
-
move的返回类型是string&&
-
move的参数__t类型为string& &&, 会被折叠为string&.
-
因此std::move最终被实例化如下, __t类型是String&, 因此cast将__t类型string&转为string&&.
string&& moe(string& __t){ return static_cast<string &&>(__t); }
通过以上分析, 知道std::move, 无论传递左值/右值, 我们都可以获取到一个右值引用。 传递左值的时候,函数会使用cast进行强制转化, 传递右值,则什么都不用做。
4. forward
template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); }
std::forward实现, forward提供两个重载版本, 一个针对左值, 一个针对右值。
template<typename T> void foo(T&& fparam) { std::forward<T>(fparam); } int i = 7; foo(i); foo(47);
在foo(i), 如果传入的是一个左值, 那么foo中T的类型将是int&, fparam类型是int& &&, 经过折叠为int&. 因此,在std::forward模板函数中,
-
推断出T的类型为int&
-
因此, std::remove_reference用int& 进行实例化
-
std::remove_reference的type成员是int
-
forward返回类型为int& &&, 折叠为int&
-
forward的参数类型__t为int&
-
static_cast 折叠为static_cast
因此std::forward最终被实例化如下。因此可以发现,函数什么都不用做, 最终的传入forward的左值引用被保留了。
int &forward(int &__t){ return static_cast<int &>(__t) }
-
在foo(47)中, 传入的是一个右值,那么foo中T的类型将是int, fparam类型是T&&, 因此,在std::forward模板函数中
-
推断出T的类型为int
-
因此, std::remove_reference用int 进行实例化
-
std::remove_reference的type成员是int
-
forward返回类型为int&&
-
forward的参数类型__t为int&&
-
static_cast
因此std::forward最终被实例化如下。因此可以发现,函数什么都不用做, 最终的传入forward的右值引用被保留了。
int &&forward(int &&__t){ return static_cast<int &&>(__t) }
无论传递左值还是右值, forward都可以完美转发。