详解C++中地左值、右值和移动

转载一篇我自己写在CSDN上的一篇文章

左值持久,右值短暂

C++ primer中提到过:当一个对象被用作右值时用的是对象的值(内容),当对象被用做左值时用的是对象的身份(在内存中的位置)^{[primer]}

int a = 10;			//a是左值,10是右值
//编译出的汇编代码
movl    $10, -4(%rbp)//-4(%rbp)是栈上的偏移,可以理解为a的地址,10是一个立即数

通过以上可以理解,左值是内存,右值是值了。同时也就能理解:左值持久是指直到变量销毁前都一直存在;右值短暂是指值10是只存在于CPU中的某个瞬间,当这个时间过去后,值10便消失不见;只有当一个右值被存进一个左值当中时,这个右值才能持续存在。

两种右值

一种右值就是上边提到的立即数,另一种是临时变量。其实立即数就是内置类型的临时变量。

struct TypeA{ TypeA(); };
10;					//右值
TypeA();			//右值,离开该行后将不存在

左右值引用

左值引用非常常见,是一个对象的别名。

//左值引用
int lvalue;
int &lref = lvalue;
//汇编结果
        .type   lvalue, @object
        .size   lvalue, 4
lvalue:
        .zero   4
        .type   lref, @object
        .size   lref, 8
lref:
        .quad   lvalue
//const 指针
int lvalue;
int * const lref = &lvalue;		//lref不能指向别处
//汇编结果
        .type   lvalue, @object
        .size   lvalue, 4
lvalue:
        .zero   4
        .type   _ZL4lref, @object
        .size   _ZL4lref, 8
_ZL4lref:
        .quad   lvalue

从左值引用和const指针的汇编结果可以看出,左值引用就是const指针。在64位系统中左值引用和const指针都占用8Byte。我想c++在c语言之上提出了左值引用的概念是为了,简化指针的操作。

右值引用是一个值的别名。当创建一个右值引用变量时,其本质是将一个右值存储了起来,延续了这个右值的生命周期,再将const指针绑定到该存储空间上,并且该存储空间没有变量名能直接访问。

//用变量存储右值
int lvalue = 10;
//汇编结果
        .type   lvalue, @object
        .size   lvalue, 4
lvalue:
        .long   10
//右值引用绑定右值
int &&rref = 10;
//汇编结果
        .type   _ZGR4rref_, @object
        .size   _ZGR4rref_, 4
_ZGR4rref_:
        .long   10
        .type   rref, @object
        .size   rref, 8
rref:
        .quad   _ZGR4rref_

移动构造和移动赋值函数的调用时机

#include <iostream>
#include <utility>
class TypeA
{
public:
        TypeA() = default;
        TypeA(const TypeA&) = default;
        TypeA& operator=(const TypeA&) = default;
        TypeA(TypeA&&) = default;
        TypeA& operator=(TypeA&&) = default;
        TypeA& operator+(const TypeA&) = default;
};

int main(){
        TypeA a1;				//调用无参构造,创建出 a1_
        a1 = TypeA();			//TypeA()是个右值,调用移动赋值
        TypeA a2(TypeA{12});	 //1调用无参构造,构造出临时变量
    						  //2调用移动构造
    
        a3 = std::move(a1);		 //调用std::move将左值转换成右值
    						  //调用移动赋值函数
        TypeA a4(std::move(a1);  //调用std::move将左值转换成右值
    						  //调用移动构造函数
                 
        TypeA a5(a);			//调用拷贝构造
        a5 = a1;			    //调用拷贝赋值
        return 0;
}

这里的调用关系本质上是函数重载时,的优先匹配问题。如果向构造函数和赋值函数中传入右值,最佳匹配的就是移动构造和移动赋值;相应的如果传入的是左值,则最佳匹配将是拷贝构造和拷贝赋值。std::move将在后文详述。

右值与右值引用

由于右值引用只能绑定到临时对象,我们得知^{[primer]}

  • 所有引用的对象将要被销毁
  • 该对象没有其他用户(ps:应为是我们延续了该右值的生命周期)

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

演示资源的转移

#include <iostream>
#include <cstdlib>

class TypeA
{
public:
    int *Buffer;
    const static size_t bufSize;
public:
    TypeA() : Buffer(nullptr) {
        //获取资源
        Buffer = (int*)malloc(bufSize*sizeof(int));
        //初始化资源
        for(int i=0; i<bufSize; ++i)
            Buffer[i] = i;
        std::cout << "Buffer: " << Buffer << std::endl;
    }
    ~TypeA(){
        //释放资源
        free(Buffer);
        Buffer = nullptr;
    }

    TypeA(const TypeA& a) : Buffer(nullptr){
        //获取资源
        Buffer = (int*)malloc(bufSize*sizeof(int));
        //拷贝资源值
        for(int i=0; i<bufSize; ++i)
            Buffer[i] = a.Buffer[i];
    }
    TypeA& operator=(const TypeA& a){
        //释放旧资源
        free(Buffer);
        //获取新资源
        Buffer = (int*)malloc(bufSize*sizeof(int));
        //拷贝资源值
        for(int i=0; i<bufSize; ++i)
            Buffer[i] = a.Buffer[i];
        return *this;
    }

    TypeA(TypeA&& a) : Buffer(nullptr){
        //盗取a的资源,因为右值a将会被销毁
        Buffer = a.Buffer;
        //将右值a的资源做为无效
        a.Buffer = nullptr;
    }
    TypeA& operator=(TypeA&& a){
        //释放旧资源
        free(Buffer);
        //盗取a的资源,因为右值a将会被销毁
        Buffer = a.Buffer;
        //将右值a的资源做为无效
        a.Buffer = nullptr;
        return *this;
    }

    void printBufferAddr(){
        std::cout << "Buffer: " << Buffer << std::endl;
    }
};

const size_t TypeA::bufSize = 1*1024*1024;   //1 MB

int main()
{
    //三份资源
    TypeA a1;
    TypeA a2(a1);
    TypeA a3 = a1;
    std::cout << "a1\t";
    a1.printBufferAddr();
    std::cout << "a2\t";
    a2.printBufferAddr();
    std::cout << "a3\t";
    a3.printBufferAddr();
	//窃取临时变量,std::move()处理后的变量的资源
    TypeA arf1 = TypeA();
    std::cout << "arf1\t";
    arf1.printBufferAddr();
    TypeA arf2 = std::move(a1);
    std::cout << "arf2\t";
    arf2.printBufferAddr();
    std::cout << "a1\t";
    a1.printBufferAddr();
}

//执行结果
//通过深拷贝,获取了三份不同的资源
Buffer: 0x7f81bdeb9010			//a1的资源地址
a1      Buffer: 0x7f81bdeb9010
a2      Buffer: 0x7f81bdab8010
a3      Buffer: 0x7f81bd6b7010
//通过移动构造和移动赋值,可窃取临时变量或std::move()处理后的资源
Buffer: 0x7f81bd2b6010			//临时变量的地址
arf1    Buffer: 0x7f81bd2b6010
arf2    Buffer: 0x7f81bdeb9010
a1      Buffer: 0

std::move

std::move是对于左值的一种承诺,承诺这个左值在之后将会被销毁,或者会重新初始化。这样依赖编译器就可以将左值当作右值处理,在调用构造和赋值函数的重载函数簇时,就顺理成章地匹配移动构造和移动赋值版本。(需要注意的是:std::move返回的是右值,所有不能被绑定到拷贝构造和拷贝赋值上,因而如果一个类没有定义移动构造和移动赋值,景观可以正确地调用std::move(),但不能将std::move的结果出入构造和赋值函数,也就失去了意义。)

std::move源码

namespace std
{
     template<typename T>
	typename remove_reference<T>::type&& move(T&& t)
	{
		return static_cast<typename remove_reference<T>::type&&>(t);
	}
}

左值可以向右值转换:

int a=3, b=2;
//这里隐式地将a,b转换成了右值
int c = a+b;
//这里显式地将a,b转换成了右值
int c = static_cast<int>(a) +static_cast<int>(b);

引用折叠:

X& &,X& &&,X&& &,折叠成X&

X&& && 折叠成 X&&

//向move中传递左值
int a;
move(a);
//模板被示例化成
int&& move(int&&);
T -> int;
typename remove_reference<int>::type  ->  int;
typename remove_reference<int>::type&&   ->  int&&;

//向move中传递右值
move(10);
//模板被实例化成
int&& move(int& &&);
int& && 折叠成了 int&;
T -> int&;
typename remove_reference<int&>::type  ->  int;
typename remove_reference<T>::type&&   ->  int&&;

所以,无论向std::move中传入左值还是右值,std::move返回地都是该类型地右值

#C++11新特性#
全部评论

相关推荐

真tmd的恶心,1.面试开始先说我讲简历讲得不好,要怎样讲怎样讲,先讲背景,再讲技术,然后再讲提升多少多少,一顿说教。2.接着讲项目,我先把背景讲完,开始讲重点,面试官立即打断说讲一下重点,无语。3.接着聊到了项目的对比学习的正样本采样,说我正样本采样是错的,我解释了十几分钟,还是说我错的,我在上一家实习用这个方法能work,并经过市场的检验,并且是顶会论文的复现,再怎么不对也不可能是错的。4.面试官,说都没说面试结束就退出会议,把面试者晾在会议里面,丝毫不尊重面试者难受的点:1.一开始是讲得不好是欣然接受的,毕竟是学习。2.我按照面试官的要求,先讲背景,再讲技术。当我讲完背景再讲技术的时候(甚至已经开始蹦出了几个技术名词),凭什么打断我说讲重点,是不能听出人家重点开始了?这也能理解,每个人都有犯错,我也没放心上。3.我自己做过的项目,我了解得肯定比他多,他这样贬低我做过的项目,说我的工作是错误的,作为一个技术人员,我是完全不能接受的,因此我就和他解释,但无论怎么解释都说我错。凭什么,作为面试官自己不了解相关技术,别人用这个方式work,凭什么还认为这个方法是错的,不接受面试者的解释。4.这个无可厚非,作为面试官,不打招呼就退出会议,把面试者晾着,本身就是有问题。综上所述,我现在不觉得第一第二点也是我的问题,面试官有很大的问题,就是专门恶心人的,总结面试官说教,不尊重面试者,打击面试者,不接受好的面试者,技术一般的守旧固执分子。有这种人部门有这种人怎么发展啊。最后去查了一下,岗位关闭了。也有可能是招到人了来恶心人的,但是也很cs
牛客20646354...:招黑奴啊,算法工程师一天200?
点赞 评论 收藏
分享
评论
7
8
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务