C++11特性(二)

C++
1.1k words

右值引用和移动语义

左值引用和右值引用

所谓的引用就是给变量起别名,那么左值引用和右值引用的区别其实就在于左值和右值

左值与左值引用

左值表示的是一个数据的表达式,比如说变量的名字,或者是解引用的指针,我们可以获取左值的地址,并且可以对左值赋值,左值可以出现在赋值符号的左边,而右值不行

const的左值不能赋值但是可以取地址,左值引用就是给左值取引用,左值引用依然是左值

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
// 左值
int a = 1;
int* pa = &a;
const int b = 2;

// 左值引用
int& ra = a;
int*& rpa = pa;
const int& b = b;
int& rpaval = *pa;
return 0;
}

这些用法是我们可以容易理解的

右值与右值引用

右值的本质上也是数据的表达式,但是与左值有不同,例如字面常量,表达式的返回值,函数的返回值(非左值引用返回),右值可以出现在赋值符号的右边,不能取地址

右值引用同样也是对右值取别名,但是取别名之后不能进行赋值等操作(因为赋值的左边只能是左值),并且右值的引用是左值

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
double x = 11,4, y = 51,4;

// 右值
5.21;
x + y;
add(x,y);

// 右值引用
int&& rr1 = 1;
double&& rr2 = x + y;
double&& rr3 = add(x,y);
return 0;
}

当我们把右值放在赋值符号左边时,就会报C2106错误,赋值符号的左操作数必须是左值

右值不能取地址,但是右值取别名后会背存储到特定的位置,并且可以取到这个位置的地址

也就是说,不能直接对数字11.4取地址,但是对他右值引用rr1,就可以取地址了,甚至可以修改

需要注意的是,左值引用只能引用左值,不能引用右值,但是const左值引用可以引用左值和右值

1
2
3
4
5
6
7
8
9
int main()
{
int a = 0;
int& ra = a;

const int& rra = 1;
const int& rrb = a;
return 0;
}

右值引用只能引用右值,不能直接引用左值,但是可以引用move之后的左值

1
2
3
4
5
6
7
int main()
{
int&& r1 = 1;
int a = 1;
int&& r2 = std::move(a);
return 0;
}

右值引用有什么用

我们把move之后的左值称之为将亡值,这么叫还是有一些原因的

当初我们在实现vector的时候,进行赋值重载的时候,我们是直接把临时对象中的三个指针和构造出来的三个指针直接进行对换,并没有真正进行拷贝

考虑到这样做的效率确实很高,move的存在也就是为了对变量也能做到这样

如果一个对象存在就是为了去构造别的对象,拿为什么不直接把这个数据直接给他呢

1
2
3
4
5
6
7
8
9
10
11
12
string to_string(int val)
{
string ret;
// ..
return ret;
}

int main()
{
string s1 = to_string(100);
return 0;
}

这里的ret首先会拷贝构造给一个临时对象,然后这个临时对象再拷贝构造给s1

这里的临时对象其实就很多余,这时我们可以写一个右值引用的拷贝构造,因为编译器会调用最合适的那个,他就会调用这个我们写的交换的逻辑,而不是那个逐一拷贝的深拷贝了

1
2
3
4
5
6
7
8
string(string&& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
swap(s);
}

这种构造我们称之为移动构造,现象叫做移动语义

同样的我们还可以实现移动赋值

需要注意的是,当连续两次进行拷贝构造的时候,如果我们不实现移动构造,他调用的还是深拷贝的那个版本,因此还是移动构造略胜一筹

完美转发与万能引用

在函数这里直接写左值或者右值的引用其实只能限制其接收的参数类型,在之后的使用中都会变成左值,为例解决这个问题C++11提出了万能引用,也就是说他既可以接收左值也可以接收右值,并且能保持原来的属性,我们管这个东西也叫做完美转发

1
2
3
4
5
template<typename T>
void Func(T&& t)
{

}

Comments