您的位置:首页 > 编程语言 > C语言/C++

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

2016-01-24 18:49 295 查看

一、左值和右值

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

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

例子:

[code]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是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。

二、左值引用

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


对于常量左值引用:

[code]const int& v1 = 5;
const int v2 = 5;


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

三、右值引用

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

[code]int && num = 8; //右值引用


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

[code]    //右值引用只能绑定到右值
    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;


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

[code]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语义

我们看以下代码:

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


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

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

[code]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构造函数)。

对于强制被强制转化为右值的左值,其生命周期并不会有所改变,但是由于它的值已经转移给右值引用了,所以我们应该保证不再使用它的值,否则将会出错,我们能做的就是给它赋新值或者销毁它。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: