C++11 浅谈 右值引用和move语义

一、左值和右值

lvalue: 具有存储性质的对象,要实际占用内存空间、内存地址;位于赋值运算符左边时可以赋值(同时也可以用在右边)。

rvalue:没有存储性质的对象, 也就是临时对象。位于赋值运算符左边时不可赋值。

例子:

int a = 10;
int b = 20;
int *pFlag = &a;
vector<int> vctTemp;
vctTemp.push_back(1);
string str1 = "hello ";
string str2 = "world";
const int &m = 1;

a和b都是持久对象(可以对其取地址),是左值;
a+b是临时对象(不可以对其取地址),是右值;
a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值;
++a则是使持久对象a的值加1,并返回那个持久对象a本身(可以对其取地址),故其是左值;
pFlag和*pFlag都是持久对象(可以对其取地址),是左值;
vctTemp[0]调用了重载的[]操作符,而[]操作符返回的是一个int &,为持久对象(可以对其取地址),是左值;
100和string(“hello”)是临时对象(不可以对其取地址),是右值;
str1是持久对象(可以对其取地址),是左值;
str1+str2是调用了+操作符,而+操作符返回的是一个string(不可以对其取地址),故其为右值;
m是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。

二、左值引用

int a = 5int& v1 = a; //非常量左值引用,只能绑定到非常量左值,不能绑定到常量左值以及任何右值
const int& v2 = 5//常量左值引用,可以绑定到所有类型的值

对于常量左值引用:

const int& v1 = 5;
const int v2 = 5;

两者的区别是:前者直接使用了右值并为其“续命”,而后者的右值在表达式结束后就销毁了。

三、右值引用

右值引用所引用的是右值,因此一般为临时变量,标记为T &&。

int && num = 8; //右值引用

正常情况下,右值”8“在表达式语句结束后,其生命也就终结了(通常我们也称其具有表达式生命期),而通过右值引用的声明,该右值又“重获新生”,其生命期将与右值引用类型变量num的生命期一样。只要num还“活着”,该右值临时量将会一直“存活”下去。

    //右值引用只能绑定到右值
    int a = 5;
    const int b = 5;

    int&& v1 = 5;
    int&& v2 = a;   //compile error,a为左值
    int&& v3 = b;   //compile error,b为左值
    int&& v4 = a + b;

注: 基于安全考虑,具有名字的声明为右值的参数不会被认定为右值。

bool is_r_value(int &&) { return true; }
bool is_r_value(const int &) { return false; }

void test(int && i)
{
    bool isRValue = is_r_value(i); // i为有名字的变量,即使声明为右值也不会被认为是右值。false
}

四、move语义

我们看以下代码:

//执行深拷贝
String(const String& rhs): data_(new char[rhs.size() + 1])
{
    strcpy(data_, rhs.c_str());
}

这里进行了内存分配和拷贝数据,如果rhs是个临时对象,要是能将rhs的数据“move”到data_中岂不是提高了运行效率,这样子你即不需要为data_重新分配内存,又不需要去释放rhs的内存,简直两全其美。

在C++11中,右值引用就可以用来干这事:

String(String&& rhs): data_(rhs.data_)
{
    rhs.data_ = nullptr;
}

上面是一个move拷贝构造函数,它并没有进行深拷贝,而是将rhs.data_这个指针的所有者转移到了另一个对象,并将rhs的data_指针置为空。这样子,对于临时值我们只需要做浅拷贝,而避免了深拷贝带来的性能损失问题。

在 C++11,一个std::vector的 “move 构造函数” 对某个vector的右值引用可以单纯地从右值复制其内部 C-style 数组的指针到新的 vector,然后留下空的右值。这个操作不需要数组的复制,而且空的临时对象的析构也不会摧毁内存。如果vector没有 move 构造函数,那么复制构造函数将被调用,这时进行深拷贝。

move语义:将对象资源的所有权从一个对象转移到另一个对象,没有内存的拷贝。

C++11还提供了std::move方法来将左值转换为右值,从而普通的左值也可以借助move语义来优化性能。

注意,如果是一些基本类型比如int和char [10]定长数组类型,使用move仍然会发生拷贝(因为没有对应的move构造函数)。

对于强制被强制转化为右值的左值,其生命周期并不会有所改变,但是由于它的值已经转移给右值引用了,所以我们应该保证不再使用它的值,否则将会出错,我们能做的就是给它赋新值或者销毁它。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值