zoukankan      html  css  js  c++  java
  • C++的右值引用和move语法

    C++11有一个非常重要的新功能,就是推出了右值引用的概念。那么什么是右值引用,它的意义在什么地方,本文将对此予以解释。

    为什么要有右值引用

    我们先来看一段代码:

    class A
    {
    public:
        A() {
    	std::cout << "construct A" << std::endl;
    	val = new int(1); }
        ~A() 
        { 
    	delete val; 
    	std::cout << "destruct A" << std::endl;
        }
        A(const A&a) {
    	std::cout << "copy construct A" << std::endl;
    	val = new int(*a.val);
        }
        int * val = nullptr;
    };
    
    int main()
    {
        A a;
        A b = a;
        return 0;
    }
    

    执行上述的代码,得到的输出为:

    construct A
    copy construct A
    destruct A
    destruct A
    

    最开始的构造函数是在创建a,接下来从a赋值给b是调用了b的拷贝构造函数。最后程序结束的时候调用了a和b的析构函数。
    问题就在于中间的这个拷贝构造函数:对于存在堆中分配的成员的类(比如类A中有一个val值需要在创建时new出来)来说,它的拷贝构造函数一般需要根据传入的值从堆中为其分配内存,如果是一个比较大的对象的话,就会在分配内存上花费比较多的时间。
    这只是一个简单的案例,考虑这样一种情况:

    A getA()
    {
        return A();
    }
    A a = getA();
    

    上述代码如果没有经过返回值优化的话,会在getA函数return时返回一个临时变量,该临时变量会调用拷贝构造函数从return后面创建的A对象中得到值,同时A对象本身销毁;然后a会再次调用拷贝构造函数从临时对象处赋值,同时也销毁临时对象。这样其实一共调用了一次构造函数(return A()),两次拷贝构造函数(函数内创建的A对象赋值给临时变量,临时变量赋值给a),两次析构函数(临时变量和函数内创建的A对象),每次构造和析构的过程都要new和delete资源,这对于计算来说是极大的浪费。

    移动构造函数和右值引用

    为此,C++11提出了移动构造函数和右值引用来解决这个问题。
    我们看看运用了移动构造函数的A:

    class A
    {
    public:
        A() {
            std::cout << "construct A" << std::endl;
            val = new int(1); }
        ~A() 
        { 
    	delete val; 
    	std::cout << "destruct A" << std::endl;
        }
        A(const A&a) {
    	std::cout << "copy construct A" << std::endl;
    	val = new int(*a.val);
        }
        //注意这个移动构造函数
        A(A&&a) :val(a.val)
        {
    	a.val = nullptr;
        }
        int * val = nullptr;
    };
    

    上述代码中的A(A&&a)被称作移动构造函数,它的参数A&&a就是一个右值引用。该函数的作用是将传入的参数a的val的地址直接移植到自己的val中,相当于自己“偷”来了a.val的内存,并且需要在函数内将a.val的值置为空(否则会有两个对象对该内存持有引用,则析构的时候就会delete两次)。这么一来,就省略了拷贝构造函数分配内存的过程,大大优化了性能。
    关于左值和右值,可以简单的理解为左值是赋值时等号左边的变量,它可以通过&运算符取到内存;而右值则包括纯右值和将亡值。纯右值类似于a=1+1这个代码等号右边的表达式,不能取到它的内存;将亡值就是和右值引用有关的变量,之所以叫做“将亡值”是因为它一般很快就会被移动拷贝到其他的对象中,命不久矣。

    move函数

    那么如何产生一个右值引用呢?C++提供了move函数,它可以将一个左值强行转化为右值引用,相当于干了以下的事情:

    T&& t1 = static_cast<T&&>(t);
    

    那么,就可以通过move来实现移动构造的过程了:

    A a;
    std::cout << a.val << std::endl;
    A && b = std::move(a);
    *b.val = 2;
    std::cout << b.val << std::endl;
    A c(std::move(b));
    std::cout << c.val << std::endl;
    

    通过观察输出,可以发现a、b、c的val都是同一个地址,并且在生成c的过程中,不用再调用c的拷贝构造函数,而是调用移动构造函数,直接将b.val的内存赋值给c,省去了分配内存的过程。
    另外,需要注意的一点是,如果A类没有实现移动构造函数,那么由b构造c的时候仍然调用c的拷贝构造函数,这也是一种相当安全的做法————移动不成,至少还可以拷贝。

  • 相关阅读:
    Linux 清理boot分区
    Linux 虚拟内存
    使用mongoskin操作MongoDB
    nodejs操作session和cookie
    nodejs接收get参数和post参数
    Tomcat--在IDEA创建Java Web项目,通过tomcat启动
    Tomcat--配置
    Mac安装Tomcat
    XML--解析
    XML--约束
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/12662746.html
Copyright © 2011-2022 走看看