std::move
std::move
的源码长这样:
template <class _Tp>
_LIBCPP_NODISCARD_EXT inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR typename remove_reference<_Tp>::type&&
move(_Tp&& __t) _NOEXCEPT {
typedef _LIBCPP_NODEBUG typename remove_reference<_Tp>::type _Up;
return static_cast<_Up&&>(__t);
}
它本质上是将一个引用强制转换成了右值引用
。
万能引用(Universal Reference)
当我们使用 T && x
或者 auto && x
这种需要类型推断的右值形式的时候,就会被编译器作为万能引用。所谓万能引用,就是既能绑定左值引用,又能绑定右值引用。(所以说,在 C++ 中,并不是说两个 &
就代表是右值引用)。
引用折叠(Reference Collapse)
- 左值-左值 T& &:函数定义的形参类型是左值引用,传入的实参是左值引用
- 左值-右值 T& &&:函数定义的形参类型是左值引用,传入的实参是右值引用
- 右值-左值 T&& &:函数定义的形参类型是右值引用,传入的实参是左值引用
- 右值-右值 T&& &&:函数定义的形参类型是右值引用,传入的实参是右值引用
但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:
所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。
#include <iostream>
template <typename T>
void PrintInner(T &x)
{
std::cout << "I got a lvalue: " << x << std::endl;
}
template <typename T>
void PrintInner(T &&x)
{
std::cout << "I got a rvalue: " << x << std::endl;
}
template <typename T>
void Print(T &&x)
{
PrintInner(x);
PrintInner(std::move(x));
PrintInner(std::forward<T>(x));
}
int main()
{
Print(5);
std::cout << "-------------------" << std::endl;
int x = 20;
Print(x);
std::cout << "-------------------" << std::endl;
auto &&x2 = 30;
std::cout << std::boolalpha << (typeid(x2) == typeid(int &&)) << std::endl;
std::cout << "-------------------" << std::endl;
auto &&x3 = x;
std::cout << std::boolalpha << (typeid(x3) == typeid(int &)) << std::endl;
}
上面这个例子最终会输出:
I got a lvalue: 5
I got a rvalue: 5
I got a rvalue: 5
-------------------
I got a lvalue: 20
I got a rvalue: 20
I got a lvalue: 20
-------------------
true
-------------------
true
观察:
- 虽然 5 是作为右值被传入函数
Print
,但是这个右值被绑定到了形参x
上,所以PrintInner
最终调用的是左值引用的版本。 std::move
毋庸置疑,本质上就是都强制转换成右值引用。std::forward
为了将 5 继续当作右值转发,我们可以使用std::forward
,也就是 完美转发。- 事实上,可以将 T && x 和 T &x 合并成 T &&,因为我们有万能引用。
常左值引用可以绑定一个右值。
const std::string && func() {
return std::move(std::string("ok"));
}
const std::string &ok = func(); // ok
const std::string &ok = "ok"; // ok
std::string &fail = "fail";
error: non-const lvalue reference to type 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char>>') cannot bind to a value of unrelated type 'const char[5]'
std::string &fail = "fail";