右值,移动语义,完美转发
先来几个快速问答:
右值什么时候出现的?右值早就有了,右值引用直到c++11才有的
右值有什么用?通过右值引用,减少拷贝。
为什么右值引用能减少拷贝?右值赋值给右值引用变量的时候,直接将右值对应的地址交给右值引用变量,相当于对应的内存换了个马甲。
什么时候用右值引用?一般都是将左值转换成右值,然后赋值给右值引用,来减少拷贝。
完美转发是个什么玩意?右值引用的赋值只能用一次,函数嵌套需要多次就需要完美转发了。
来几个例子
int a = 10; int b = 20; int *pFlag = &a; vector<int> vctTemp; vctTemp.push_back(1); string str1 = "hello"; string str2 = "world"; const int &m = 1;
请问,a,b, a+b, a++, ++a, pFlag, *pFlag, vctTemp[0], 100, "hello", str1, str1+str2, m分别是左值还是右值?
a和b都是持久对象(可以对其取地址),是左值;
a+b是临时对象(不可以对其取地址),是右值;
a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值;
++a则是使持久对象a的值加1,并返回那个持久对象a本身(可以对其取地址),故其是左值;
pFlag和*pFlag都是持久对象(可以对其取地址),是左值;
vctTemp[0]调用了重载的[]操作符,而[]操作符返回的是一个int &,为持久对象(可以对其取地址),是左值;
100是数值字面量,不可以取地址,是右值;
"hello"是字符串字面量,但在C++中是可以取地址的,所以是左值
str1是持久对象(可以对其取地址),是左值;
str1+str2是调用了+操作符,而+操作符返回的是一个string(不可以对其取地址),故其为右值;
m是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。
上面例子的右值都是纯右值,纯右值还包括:非字符串字面量,临时对象,函数返回的非引用对象。
还有一类右值,称为将亡值,包括:函数返回右值引用对象,通过static_cast<T&&>()和std::move<T>()强制转换后的对象
非常量左值引用只能获取非常量左值,常量左值引用可以获取任意的值。
非常量右值引用只能获取非常量右值,常量右值引用只能获取常量右值或非常量右值
为什么?
非常量肯定只能获取非常量。不然获取常量了,那个常量就可以变了?不合理。
常量可以获取非常量,然后这个非常量就不能变了。
非常量左值只能获取左值,因为右值在没有被赋值前都是常量
但是有一点要注意,看到&&不一定都是右值引用,还有可能是万能引用
例如:
template<typename T> T add(T&& a, T&& b) { return a+b; }
这里并不知道T具体是什么类型,只有运行的时候才知道
这里的T&&既可以是右值引用,也可以是左值引用,叫万能引用
到底是什么要看你传进去个什么
如果 T 传进 int左值, 那 T 就是 int&
如果 T 传进 int右值,那 T 就是 int
如果 T 传进 int左值引用,那 T 就是 int&
如果 T 传进 int右值引用,那 T 就是 int&&
然后 T&& 再发生引用折叠
int& && --> int&
int && --> int&&
int&& && --> int&&
万能引用就是这么怪
注意,如果T不是推到型,就不存在上面的规则
例如下面的情况不是万能引用
template<typename T> T add(std::vector<T>&& vec) { ... }
因为vec传进来已经确定类型了,它就代表std::vector<T>的右值引用
还有:
int&& r = 10; int x = add(r,20);
这里的r虽然是右值引用,但是,r本身是左值! T&& 就是 int&
除非:
int x = add(std::move(r),20);
T&& 才是 int&&
做个实验
class shape { public: shape() {} virtual ~shape() {} }; void foo(const shape&) { puts("foo(const shape&)"); } void foo(shape&&) { puts("foo(shape&&)"); } void bar(const shape& s) { puts("bar(const shape&)"); foo(s); } void bar(shape&& s) { puts("bar(shape&&)"); foo(s); } int main() { bar(shape()); }
输出结果是:
bar(shape&&)
foo(const shape&)
shape()生成一个临时对象,是纯右值,优先调用void bar(shape&& s)
内部foo(s)时,s是左值,优先调用void foo(const shape&)
所以在函数嵌套调用的时候,参数的类型就变了,本来是右值引用的,就成左值引用了,有什么办法能不变吗?
有,参数用完美转发(forward)包一下,forward代码如下
template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } template<class _Ty> inline constexpr _Ty&& forward( typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT { static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call"); return (static_cast<_Ty&&>(_Arg)); }
当传入是左值时, _Tp推到为左值引用,强制转化时折叠成左值引用,返回还是左值引用
当传入是右值时, _Tp推到为右值, 强制转化时折叠成右值引用,返回还是右值引用
当传入是左值引用时,_Tp推到为左值引用,强制转化时折叠成左值引用,返回还是左值引用
当传入是右值引用时,_Tp推到为右值引用,强制转化时折叠成右值引用,返回还是右值引用
再看下move的代码
template<typename _Tp> constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
_Tp推到成什么不重要,重要的是,强制转化为右值引用,然后返回还是折叠成右值引用。
总结下:
forward()的作用是传进去是什么手性(左右), 返回还是什么手性
move() 的作用是不管传进去什么手性(左右),返回都是右值引用
下面是一个万能函数包装器,任何参数都可以"完美转发"
注意下面的...不是省略,是可变参数语法
template<class Function, class... Args> inline auto FuncWrapper(Function && f, Args && ... args) -> decltype(f(std::forward<Args>(args)...)) { //typedef decltype(f(std::forward<Args>(args)...)) ReturnType; return f(std::forward<Args>(args)...); //your code; you can use the above typedef. }