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
    评论
右值引用和move语义是C++ 11中重要的特性之一,可以提高程序的效率和性能。右值引用是一种新的引用类型,其绑定到临时对象或将要销毁的对象上,而不是左值对象。move语义则是利用右值引用,将一个对象的资源所有权从一个对象转移到另一个对象,避免了不必要的内存拷贝,提高了程序的效率。 下面是一个使用右值引用和move语义的例子: ```c++ #include <iostream> #include <vector> using namespace std; vector<int> getVector() { vector<int> v = {1, 2, 3, 4}; return v; } int main() { vector<int> v1 = getVector(); // 拷贝构造函数 vector<int> v2 = move(v1); // 移动构造函数 cout << "v1 size: " << v1.size() << endl; // 输出 0 cout << "v2 size: " << v2.size() << endl; // 输出 4 return 0; } ``` 在上面的例子中,getVector函数返回一个临时对象vector<int>,该临时对象是一个右值。在主函数中,我们使用拷贝构造函数将临时对象的值拷贝到v1中,然后使用move函数将v1中的值移动到v2中。由于move函数使用了右值引用,将v1中的资源所有权转移到了v2中,避免了不必要的内存拷贝,提高了程序的效率。最后,我们输出v1和v2的大小,可以看到v1的大小为0,v2的大小为4,说明资源已经成功转移。 需要注意的是,使用move语义之后,原对象的值被移动到新对象中,并且原对象的值被置为默认值(例如,对于vector而言,原对象的大小为0)。如果需要保留原对象的值,则需要在移动之前先进行一次拷贝操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值