0%

右值引用

c++右值引用,universal reference,移动语义和完美转发

右值不同于左值,无法取地址,例如常量表达式的结果,某些返回值。
引用本质上就是指针。右值引用就是能被右值初始化的引用。

  • 相比于左值引用初始化后不能再改变,右值引用初始化后可以改变。
  • 右值引用能延长右值的生命周期,但是这不是c++11提出右值引用的关键(因为const左值引用也能被右值初始化)

移动语义

1
2
3
4
5
6
// remove_reference_t<_Ty>会删除_Ty的引用属性
template <class _Ty>
constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept {
// forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

为了解决大对象拷贝会消耗很长时间的问题。

  1. std::move会将左值引用转化为右值引用
  2. 转化的右值引用用于构造对象(需要定义右值构造和右值赋值),其实本质上右值引用和左值引用并没有任何区别,这么区分只是为了编译器可以清楚地知道是调用左值的构造还是右值的构造。如果你喜欢,甚至可以再右值构造里面实际上做复制,左值构造里面实际上做移动。
  3. 为了实现移动语义,需要定义右值构造和右值赋值,因为我们的目的是减少拷贝时间,而传入的右值又是不应该再被使用的,所以我们可以直接把传入的参数的成员全部copy过来,并且需要将原先的成员全部变成默认值(特别是指针,因为析构可能会不小心把allocated的空间释放掉),这样就等于把原先的内容偷了过来。

std::move的作用就是告诉编译器,这个左值马上要消亡了(局部变量),当成右值来用

Universal references

  • T&& &&是右值引用
  • T& &&是左值引用
  • T&& &是左值引用
  • T& &是左值引用

这在模板类和auto的自动推导中会用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>
void f(T&& param); // param is a universal reference

int x = 27;
const int cx = x;
const int& rx = x;

f(x); // x is lvalue, so T is int&,
// param's type is also int&
f(cx); // cx is lvalue, so T is const int&,
// param's type is also const int&
f(rx); // rx is lvalue, so T is const int&,
// param's type is also const int&
f(27); // 27 is rvalue, so T is int,
// param's type is therefore int&&

完美转发

1
2
3
4
5
6
7
8
9
int main() {
string A("abc");
string&& Rval = std::move(A);
string B(Rval); // this is a copy , not move.
cout << A << endl; // output "abc"
string C(std::forward<string>(Rval)); // move
cout << A << endl; // output ""
return 0;
}

上面这段程序展现了右值引用的一个问题,右值引用变量是左值。这在函数中很成问题,假设有f(T&& a),我在f中并不需要用到a,而是需要将它传给某个其他函数,例如右值构造。但是如果我们像demo的第四行这样直接调用,实际上调用的是左值构造。

为了维持参数原来的特性,需要对参数进行完美的转发,使用std::forward来帮助实现完美转发。完美转发让左值还是左值,右值还是右值.

注意std::forward是配合泛型使用的,上面demo仅用于展现右值引用变量其实是左值的问题,因为上面demo的情况我们其实可以用std::move将其变成右值引用。

1
2
3
4
5
6
7
8
9
10
11
12
template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {
// forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}

template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {
// forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
  1. 为了实现完美转发,我们的函数f中需要完美转发的参数要是universal reference,例如f(T&& a),切记完美转发是模板函数的universal reference(的编译器类型推导)和std::forward共同作用的结果,非泛型手动std::move一下就好了,用不着完美转发
  2. 在模板类中使用std::forward对参数进行转发
1
2
3
4
template <typename T>
void f(T&& arg) {
g(std::forward<T>(arg));
}
  1. 如果给f传左值、左值引用时,T推断为int&, argint&, 调用forward<int&>(xxx& _Arg), g收到的是int&
  2. 如果给fconst变量,const属性保留(remove_reference_t不会破坏const)
  3. 如果给f传右值(例如10), T推断为int, argint&&, 调用forward<int&&>(xxx& _Arg), g收到int&&
  4. 注意,如果给f传右值引用的变量,T推到得到的是int&, g得到的是int&,因为右值引用变量是左值