右值引用、移动构造函数、move、push_back和empalce_back

⭐c++11引入右值引用,并且提供了move函数,用来获得绑定到左值上的右值引用,此函数定义在头文件utility中。

什么是左值和右值?

  • C/C++语言中可以放在赋值符号左边的变量,即具有对应的可以由用户访问的存储单元,并且能够由用户去改变其值的量。

    • 左值表示存储在计算机内存的对象,而不是常量或计算的结果。或者说左值是代表一个内存地址值 ,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作;这也就是为什么左值可以被赋值的原因了。
    • 相对应的还有右值:当一个符号或者常量放在操作符右边的时候,计算机就读取他们的“右值”,也就是其代表的真实值
  • 简单来说就是,左值相当于地址值,右值相当于数据值。右值指的是引用了一个存储在某个内存地址里的数据。

左值: 指的是表达式结束后依然存在的持久化对象。
右值: 指的是表达式结束时就不再存在的临时对象。

左值右值总结:(T是一个具体类型)

(1)左值引用,使用T&,只能绑定左值

(2)右值引用,使用T&&,只能绑定右值

(3)常量左值,使用conts T&,可以绑定左值和右值。

(4)已命名的右值引用,编译器会认为是左值。

(5)编译器有返回值优化功能,但不可过于依赖。

已命名的右值引用,编译器会认为是个左值

左值引用使用&符号,如
int a = 10;
int &refA = a; 
int &b = 1;   //编译错误,1是右值,不能使用左值引用

右值引用使用&&符号,如
int &&a = 1;
int b = 1;
int &&c = b;  //编译错误,不能将左值赋值给一个右值引用
class A
{
public:
    int a;
};
A getTemp()
{
    return A();
}
A &&a = getTemp();  //getTemp()返回值是右值(临时变量)

a的类型为右值引用类型(int &&),如果从左值和右值的角度区分它,它实际上是一个左值。
因为可以对它取地址,而且它还有名字,是一个已经命名的右值。
  • 所以,左值引用只能绑定左值,右值引用只能绑定右值。常量左值引用是个特例,它可以算一个
  • 万能的引用类型,可以绑定非常量左值,常量左值,右值,而且在绑定右值的时候,可以像右值引用一样
  • 将右值的生命期延长,缺点是只 能读不能改

左值和右值是相对于赋值表达式而言的。左值是能出现在赋值表达式左边的表达式。左值表达式可以分为可读写的左值和只读左值。右值是可以出现在赋值表达式右边的表达式,他可以是不占据内存空间的临时量或字面量,可以是不具有写入权的空间实体

左值和右值判断:
1)可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。
2)有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。

int i = 10;
10 = i;
错误,10为右值,不能当左值用
int j = 20;
j = i;
i和j都是左值,但是i可以当右值用


int i=42;
int &r=i;   //正确,r引用i
int &&rr=i   //错误,不能将一个右值引用绑定到一个左值上
int &r2=i*42;  //错误,i*42是一个右值
const int &r3=i*42;  //正确,我们可以将一个const的引用绑定到一个右值上
int &&r2=i*42; //正确,将rr2绑定到乘法结果上

以上面定义的变量 i、j 为例,i 和 j是变量名,且通过 &a 和 &b 可以获得他们的存储地址,因此 a 和 b 都是左值;反之,字面量10、20,它们既没有名称,也无法获取其存储地址(字面量通常存储在寄存器中,或者和代码存储在一起),因此10、20 都是右值。

int i = 10;
int &a = i;
int &b = 10;//错误
const int &c = 10;//正确

常规的我们使用的一个 &表示左值引用 ,普通左值引用只能接收左值,不能接收右值,但是常量左值引用既可以接收左值也可以接收右值。

int j = 10;
int &&d = 10;
int &&e = j;//错误

支持移动操作,c++新标准引入了一种新的引用类型—右值引用。所谓右值引用就是必须绑定到右值的引用。我们通过&& 而不是&来获得右值引用。如我们将要看到的,右值引用有一个重要的性质—只能绑定到一个将要销毁的对象。 因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。

&&表示右值引用 ,常规右值引用可以用来接收右值,但是不能用来接收左值

  • 1.左值持久,右值短暂
    左值有持久的状态,而右值要么是字面值常量,要么是表达式求值过程中创建的临时对象。
    由于右值引用只能绑定到临时对象,我们得知
    • 1 所引用的对象将要被销毁
    • 2 该对象没有其他用户

这两个特征意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。

  • 2.变量是左值
    变量可以看作只有一个运算对象而没有运算符的表达式,虽然我们很少这样看待变量。类似于其他任何表达式,变量表达式也有左值/右值属性。变量表达式都是左值,带来的结果就是,我们不能将一个右值引用绑定到一个右值引用类型的变量上。
int &&rr1 =42;  //正确,字面值常量是右值
int &&r2 =rr1;   //错误,表达式rr1是左值!
  • 注意: 变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不可以。

  • 因此,可以给一个这样的结论可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值。

  • 可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值。

  • ⭐从本质上理解,创建和销毁由编译器幕后控制,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象)。

move函数

  • 虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。我们可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。
int &&rr3 =std::move(rr1);  //OK
  • move调用告诉编译器:我们有一个左值,但我们希望像右值一样处理它。我们必须认识到,调用move就意味着承诺:除了对rr1赋值或者销毁之外,我们将不再使用它。在调用move之后,我们不能对移后源对象的值做任何假设。

  • 注意:

    • 1.我们可以销毁一个移后源对象,也可以赋予它新值,但是不能使用一个移后源对象的值。
    • 2.对于move的使用应该是std:move而不是move。这样做可以避免潜在的名字冲突。

⭐std::move()将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。
⭐std::forward()和universal references通用引用共同实现完美转发。

移动构造函数

移动构造函数类似于拷贝构造函数,第一参数是该类类型的一个引用,但这个引用参数在移动构造函数中是一个右值引用。

与拷贝构造函数不同,移动构造函数不分配新内存,它接管内存并把对象中的指针都置为nullptr。最终,移后源对象会被销毁,意味着将在其上运行析构函数。

vector<int> v1={1,2,3,4,5};
 
vector<int> v2=move(v1);
 
cout<<"v1.size:"<<v1.size()<<endl;
 
cout<<"v2.size:"<<v2.size()<<endl;
 
//执行结果为:
 
v1.size:0
 
v2.size:5

⭐用empalce_back()替换push_back()增加性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值