C++11中的右值引用与移动语义

什么是右值?什么是左值?

简单的来说左值就是能放在=左边的,可以被取地址的就是左值;能放在=右边的,不能取地址的就是右值,但这样来定义并不准确:

int main(){
    int a=10;//a是左值,10是右值
    int b=a;//a,b都是右值,可a放在=的左边
    //说明:左值可以放在=的左边,也可以放在=的右边
   
    const int c=30;
    //c=a;error:c是const常量,不可以被修改
    cout<<&c<,endl;//可是c可以取地址,因此c并不算左值

    //b+1=20;error:b+1是一个临时变量,没有具体名称,不可以取地址,是一个右值
    return 0;
}

准确的来说:
左值:就是可以被修改的对象,如:a,b
右值:通常是常量,表达式的返回值(临时对象),如100,a+b

更确切的来说:

  1. 普通类型的变量,有名字,可以取地址,都认为是左值。
  2. const修饰的变量,只能读不能修改,理论上来说应该当做右值对待,但是它可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为它是左值。
  3. 如果表达式的运行结果是一个临时变量或对象,认为是右值。
  4. 如果表达式运行结果或单个变量是是一个引用,则认为是左值。

C++中将右值分为两种:

  • C语言中纯右值。比如:a+b, 100
  • 将亡值。比如:表达式的中间结果、函数按照值的方式进行返回

左值引用与右值引用

  • 普通类型引用只能引用左值,不能引用右值。
  • const引用既可引用左值,也可引用右值.
int main(){
    int a=10;
    int &re1=a;//左值引用
    int &re2=20;//error:10是右值
    
    const int& re3=a;
    const int& re4=10;
    return 0;
}
C++11中的右值引用
  • 只能引用右值,一般情况不能直接引用左值。
int main(){
   //10是纯右值,本来只是一个符号,没有具体的空间,
   //右值引用变量a在定义过程中,编译器产生了一个临时变量,a实际引用的是临时变量
    int&& a=10;
    a=20;

    int b=30;
    int&& re=b;//error:右值引用不能引用左值
    return 0;
}

移动语义

  • 资源通过浅拷贝方式从一个对象转移到另一个对象。通过此方式可以减少不必要的临时对象的创建、拷贝、销毁,从而提高程序性能。
class String{
public:
String(char* str=""){
    if(nullptr==_str){
       str="";
    }
    _str=new char[strlen(str)+1];
    strcpy(_str,str);
}

String(const String& s)
    :_str(new char[strlen(s._str)]+1)
{
    strcpy(_str,s._str);    
}

String& operator=(const String& s){
    if(this!=&s){
    char* temp=new char[strlen(s._str)+1];
    strcpy(temp,s._str);
    delete _str;
    _str=temp;
    }
    return *this;
}

String operator+(const String& s){
    char* temp=new char[strlen(_str)+strlen(s._str)+1];
    strcpy(temp,_str);
    strcpy(temp,s._str);
    String strRet(temp);
    return strRet;
}
~String(){
    if(_str){
      delete[] _str;
    }
}
private:
char* _str;
};

int main(){
    String s1("hello");
    String s2("world");
    String s3(s1+s2);
    return 0;
}

上面的代码,看似没什么问题,但是在operator+ 中:

  • strRet在按照返回值返回时,会创建一个临时对象
  • 然后使用临时对象构造s3,s3构造好了以后临时对象就会销毁
  • 这三部分有自己的独立空间,存储的内容是一样的,这对于空间是一种浪费,降低了程序的效率,临时对象的作用也并不大
    在这里插入图片描述

为了解决这个问题,C++11提出了移动语句的概念。实现移动语义,就必须使用右值引用。增加移动构造:

String(String&& s)//不能设置为const型参数,否则内容无法转移
    :_str(s._str)
{
s._str=nullptr;
}
  • strRet在创建好临时对象后就销毁了,即将亡值,C++认为它是右值;
  • 在用strRet创建临时对象时,采用移动构造,也就是将strRet中的内容转移到临时对象中。
  • 临时对象也是右值,因此在使用临时对象创建s3的时候,也采用移动构造,将临时对象中的内容移动到s3中。
  • 整个过程只需要开辟一块堆内存即可,节省空间并提高了程序的运行效率。
    在这里插入图片描述
    注意:
  • 在C++11中,编译器会为类默认生成一个移动构造,这个移动构造为浅拷贝。当类涉及资源管理时,用户必须显示定义自己的移动构造。

std::move

  • 有些场景下,可能也需要右值引用引用左值去实现移动语义。可以通过std::move函数将左值强制转化为右值。
int main(){
    String s1("hello world");
    String s2(move(s1));
    String s3(s1);
}

以上代码是对move函数的误用:

  • move函数将s1转换为右值后,再实现s2的拷贝构造就会使用移动构造,此时s1中的内容转移到s2中,s1就成了无效字符串。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值