0%

cpp-右值引用,move和forward

在c++03及之前,临时值被认为不可被修改, 跟const T&无法区分开; c++11引入的一个新类型: 右值引用, 之前只有:

  • 左值: int a = 3;;
  • 左值引用: int a = 3; int& b = a;, b是右值引用;
  • 右值: 3;, 也是临时值, 包含临时值和纯右值;

1 解决什么问题

  1. 不必要的复制引起的大量内存操作

在构造和赋值操作的时候, 经常伴随着大量内存拷贝, 有些是必须的,但在有些情况下, 这种隐藏的拷贝会非常浪费,这也是c++长期存在的问题:

1
2
3
4
5
6
template <class T> swap(T& a, T& b)
{
T tmp(a); // now we have two copies of a
a = b; // now we have two copies of b
b = tmp; // now we have two copies of tmp (aka a)
}
  1. 完美转发 perfect forward

f1(a)要把参数a传递给f2(a), 可以有以下组织方式:

1
const int &ref_a = 5; // 编译正常

右值引用: 可以指向右值,不能指向左值的引用。

右值引用可以修改右值

右值引用可以指向左值 – std::move

1
2
int a = 10;
int &&ref_a_left = std::move(a);

std::move 只是强制把左值转换成右值,让右值引用可以指向左值,相当于强制类型转换;

  1. 从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。
  2. 右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
  3. 作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。

应用场景,实现容器的移动。增加了移动构造函数移动赋值重载函数

std::move 解析

1
2
3
4
5
6
7
8
9
10
11
  /**
* @brief Convert a value to an rvalue.
* @param __t A thing of arbitrary type.
* @return The parameter cast to an rvalue-reference to allow moving it.
*/
template <typename _Tp>
constexpr typename std::remove_reference<_Tp>::type &&
move(_Tp &&__t) noexcept
{
return static_cast<typename std::remove_reference<_Tp>::type &&>(__t);
}

std::move是个模板函数,constexpr 表示这个函数是编译期常量,在编译期间求值,不会增加运行时开销;typename 常在模板函数中使用,
告诉编译期,后边跟的std::remove_reference<_Tp>::type 是个类型。

std::remove_reference有3个定义, 作用是移除引用,返回值本身:

1
2
3
4
5
6
7
8
9
10
11
12
/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };

template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };

template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };

因此return static_cast<typename std::remove_reference<_Tp>::type &&>(__t); 就是强制转换成右值引用返回。

2 完美转发, std::forward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @brief Forward an lvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template <typename _Tp>
constexpr _Tp &&
forward(typename std::remove_reference<_Tp>::type &__t) noexcept
{
return static_cast<_Tp &&>(__t);
}

/**
* @brief Forward an rvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
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);
}

这里用到了引用折叠,也就是多个引用类型叠加时,最后的类型是什么?引用有左值引用、右值引用,叠加后有4种状态:

  1. 左引 - 左引: T& & – 左引
  2. 左引 - 右引: T& && – 左引
  3. 右引 - 左引: T&& & – 左引
  4. 右引 - 右引: T&& && – 右引

折叠的规则是: 有1个左引,最后就是左引。

完美转发经典场景:

1
2
3
4
template <class... Args>
void forward(Args&&... args) {
f(std::forward<Args>(args)...);
}
  • std::move 强制转换成右值
  • std::forward 是什么值,就转成什么值,中间过渡传递的。它会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。

参考

  1. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html#Move_Semantics
  2. https://stackoverflow.com/questions/3413470/what-is-stdmove-and-when-should-it-be-used
  3. https://en.wikipedia.org/wiki/C%2B%2B11#Rvalue_references_and_move_constructors