cpp左值和右值

何为右值

任何值不是左值,就是右值。左值和右值是针对表达式定义的。

  1. 右值的生存周期只到表达式结束,即语句分号后右值的生存周期就结束了。
  2. 左值可以取地址,右值不行。
  3. 左值可以放在 = 的左右,但是右值只能放在右边。

为什么要引入这么复杂的概念呢?为了性能。在赋值的时候,难免会产生一些临时变量的构造和销毁,这些构造和销毁可能会引起内存的申请与释放。频繁的系统调用会有很大的性能开销。这种时候没必要重复申请内存,只要把那个将要销毁对象内部的指针指向的空间被新的对象所利用就可以了!

1

1
2
3
4
int t = 10;  // t为左值
++t; // t为左值,没有拷贝,这就是为什么 ++t 比 t++ 高效
t++; // t为右值,因为加之前的值是个将亡值,是个 t_copy,是个临时变量
int &&r = t; // 错误,右值不能绑定左值。

std::move()

实现代码

1
2
3
4
5
6
7
8
9
/**
* @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); }

这个函数就一个作用:将左值明确的转换为右值。由于引用折叠的存在,导致返回值一定是右值。引用折叠概念见下一小节。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;
void func(string &&str) {
cout << "右值函数" << endl;
}
void func(string &str) {
cout << "左值函数" << endl;
}

int main() {
string s;
func(s + s);
func(s);
func(std::move(s));
}

结果:

1
2
3
4
@└────> # ./a.out 
右值函数
左值函数
右值函数

可以看到,s + s 是一个右值,编译器会自动匹配调用右值对应的函数。s 是一个左值,会调用左值对应的函数。使用 std::move() 强行把左值转换为右值,就匹配右值对应的函数。此处注意,因为 s 已经变为右值了,所以在调用之后,s 会失效。

std::forward()

在介绍 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
28
#include <iostream>
using namespace std;

template<typename T>
void WithoutPerfectForward(T &&t) {
cout << "lvalue:" << std::is_lvalue_reference<decltype(t)>::value << endl;
cout << "rvalue:" << std::is_rvalue_reference<decltype(t)>::value << endl;
cout << endl;
}

int main() {
int a = 1;
int b = 2;
const int c = 1;
const int d = 0;
int &e = a;
int &f = b;
const int &g = c;
const int &h = d;
WithoutPerfectForward(a); // l + r = l
WithoutPerfectForward(move(b)); // r + r = r
WithoutPerfectForward(c); // const l + r = l
WithoutPerfectForward(move(d)); // const r + r = r
WithoutPerfectForward(e); // l ref + r = l
WithoutPerfectForward(move(f)); // r ref + r = r
WithoutPerfectForward(g); // const l ref + r = l
WithoutPerfectForward(move(h)); // const r ref + r = r
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@└────> # ./a.out 
lvalue:1
rvalue:0

lvalue:0
rvalue:1

lvalue:1
rvalue:0

lvalue:0
rvalue:1

lvalue:1
rvalue:0

lvalue:0
rvalue:1

lvalue:1
rvalue:0

lvalue:0
rvalue:1

可以看到,因为 T 的不同会产生不同的模版函数特化版本,如参数会变为 T& && 和 T&& && 这两种形式,c++11 支持的编译器会对这种情况用 4 条规则做折叠处理,生成只有 & 和 && 的引用方式:

  1. & + & = &
  2. & + && = &
  3. && + & = &
  4. && + && = &&

即引用折叠时只要有左值引用,就折叠为左值引用,全部是右值引用,才折叠为右值引用

那么考虑如下场景:

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
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;

void RunCode(int && m) {
cout << "int && called!" << endl;
}
void RunCode(int &m) {
cout << "int & called!" << endl;
}
void RunCode(const int && m) {
cout << "const int && called!" << endl;
}
void RunCode(const int & m) {
cout << "const int & called!" << endl;
}

template<typename T>
void WithoutPerfectForward(T &&t) {
RunCode(t);
}

int main() {
int a = 1;
int b = 2;
const int c = 1;
const int d = 0;
int &e = a;
int &f = b;
const int &g = c;
const int &h = d;
WithoutPerfectForward(a); // l + r
WithoutPerfectForward(move(b)); // r + r
WithoutPerfectForward(c); // const l + r
WithoutPerfectForward(move(d)); // const r + r
WithoutPerfectForward(e); // l ref + r
WithoutPerfectForward(move(f)); // r ref + r
WithoutPerfectForward(g); // const l ref + r
WithoutPerfectForward(move(h)); // const r ref + r
}

结果:

1
2
3
4
5
6
7
8
9
@└────> # ./a.out 
int & called!
int & called!
const int & called!
const int & called!
int & called!
int & called!
const int & called!
const int & called!

可以看到虽然在函数 WithoutPerfectForward 中是右值,但是传给下一个函数的时候又默认都是左值了。虽然参数 t 是右值类型的,但此时 t 在内存中已经有了位置,所以 t 其实是个左值。

完美转发

为了实现不丢失传进来值的左右性,使用 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
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;

void RunCode(int && m) {
cout << "int && called!" << endl;
}
void RunCode(int &m) {
cout << "int & called!" << endl;
}
void RunCode(const int && m) {
cout << "const int && called!" << endl;
}
void RunCode(const int & m) {
cout << "const int & called!" << endl;
}

template<typename T>
void WithoutPerfectForward(T &&t) {
RunCode(std::forward<T>(t));
}

int main() {
int a = 1;
int b = 2;
const int c = 1;
const int d = 0;
int &e = a;
int &f = b;
const int &g = c;
const int &h = d;
WithoutPerfectForward(a); // l + r
WithoutPerfectForward(move(b)); // r + r
WithoutPerfectForward(c); // const l + r
WithoutPerfectForward(move(d)); // const r + r
WithoutPerfectForward(e); // l ref + r
WithoutPerfectForward(move(f)); // r ref + r
WithoutPerfectForward(g); // const l ref + r
WithoutPerfectForward(move(h)); // const r ref + r
}

结果:

1
2
3
4
5
6
7
8
9
@└────> # ./a.out 
int & called!
int && called!
const int & called!
const int && called!
int & called!
int && called!
const int & called!
const int && called!

实现代码

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
/**
* @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);
}

可以看到,std::forward() 有两个函数,会根据是左值还是右值调用不同的函数,返回不同种类出参。