• C++(11):移动构造函数


    传统上C++是通过拷贝构造函数完成通过一个对象初始化另一个对象:

    class A{
    public:
    	A()
    	{
    		m_i = new int(1);
    		cout<<"Construct A, this addr:"<<this<<", m_i addr:"<<m_i<<endl;
    	}
    	A(const A &a):m_i(new int(*a.m_i))
    	{
    		cout<<"Copy Construct A, ori addr:"<<&a<<" ori m_i addr:"<<a.m_i<<" *m_i="<<*a.m_i<<", this addr:"<<this<<" m_i addr:"<<m_i<<" *a.m_i="<<*a.m_i<<endl;
    	}
    	~A()
        {
            free(m_i);
            m_i = 0;
            cout<<"Destruct A, this addr:"<<this<<" m_i addr:"<<m_i<<endl;      
        }
    	int *m_i;
    };
     
    A getA()
    {
    	return A();
    }
     
    int main(){
    	A a3 = getA();
    	cout<<"a3 addr:"<<&a3<<" a3.m_i addr:"<<a3.m_i<<" *a3.m_i="<<*a3.m_i<<endl;
    	return 0;
    }
     
    运行程序输出:
    //return A()构造的临时对象,记作对象a1
    Construct A, this addr:0x63fdb8, m_i addr:0x6b6f90    
     
    //getA返回的临时对象,该对象是通过拷贝构造函数复制对象a1得到的,记作对象a2
    Copy Construct A, ori addr:0x63fdb8 ori m_i addr:0x6b6f90 *m_i=1, this addr:0x63fe08 m_i addr:0x6b6fb0 *a.m_i=1
     
    //对象a1析构   
    Destruct A, this addr:0x63fdb8 m_i addr:0
     
    //main函数中的A a3对象,该对象是通过拷贝构造函数复制对象a2得到的    
    Copy Construct A, ori addr:0x63fe08 ori m_i addr:0x6b6fb0 *m_i=1, this addr:0x63fe00 m_i addr:0x6b6f90 *a.m_i=1  
     
    //对象a2析构   
    Destruct A, this addr:0x63fe08 m_i addr:0
     
    //main中输出a3的信息
    a3 addr:0x63fe00 a3.m_i addr:0x6b6f90 *a3.m_i=1
     
    //对象a3析构 
    Destruct A, this addr:0x63fe00 m_i addr:0
    

    对象a2是调用拷贝构造函数,复制a1构造的,a2与a1的m_i拥有各自的内存地址,地址里的数值都是1

    对象a3是调用拷贝构造函数,复制a2构造的,a3与a2的m_i拥有各自的内存地址,地址里的数值都是1

    对象a1,a2,a3会各自分配自己的m_i指向各自的内存,对于像a1和a2这样的临时对象,不仅仅需要构造他们,还需要为他们分配和释放内存,而内存的意义就是为了让后续的对象拷贝构造,这对于分配了大量内存的对象来说,是非常低效的。

    针对这种情况C++11引入了移动构造函数的概念,可以通过它将临时对象的内存转移给后续对象,减少不必要的内存分配和释放:

    #include <iostream>
    using namespace std;
     
    class A{
    public:
    	A()
    	{
    		m_i = new int(1);
    		cout<<"Construct A, this addr:"<<this<<", m_i addr:"<<m_i<<endl;
    	}
    	A(const A &a):m_i(new int(*a.m_i))
    	{
    		cout<<"Copy Construct A, ori addr:"<<&a<<" ori m_i addr:"<<a.m_i<<" *m_i="<<*a.m_i<<", this addr:"<<this<<" m_i addr:"<<m_i<<" *a.m_i="<<*a.m_i<<endl;
    	}
    	A(A&& a):m_i(a.m_i)//移动构造函数接受一个所谓的“右值引用”的参数,完成资源的转移
    	{
    		cout<<"Move Construct A, ori addr:"<<&a<<" ori m_i addr:"<<a.m_i<<" *m_i="<<*a.m_i<<", this addr:"<<this<<" m_i addr:"<<m_i<<" *a.m_i="<<*a.m_i<<endl;
    		a.m_i = 0;
    	}
    	~A()
        {
    		cout<<"Destruct A, this addr:"<<this<<" m_i addr:"<<m_i<<endl;
    		if(m_i)
    		{
    			free(m_i);
    			m_i = 0;
    		}
        }
    	int *m_i;
    };
     
    A getA()
    {
    	return A();
    }
     
    int main(){
    	A a3 = getA();
    	cout<<"a3 addr:"<<&a3<<" a3.m_i addr:"<<a3.m_i<<" *a3.m_i="<<*a3.m_i<<endl;
    	return 0;
    }
     
    运行程序输出:
    //return A()构造的临时对象,记作对象a1
    Construct A, this addr:0x63fdb8, m_i addr:0x776f90
     
    //通过移动构造函数,将对象a1的m_i转移给getA的返回值临时对象a2;a2的m_i与a1的m_i指向相同的内存地址
    Move Construct A, ori addr:0x63fdb8 ori m_i addr:0x776f90 *m_i=1, this addr:0x63fe08 m_i addr:0x776f90 *a.m_i=1
     
    //对象a1析构,由于其资源m_i已转移给对象a2,因此无需释放内存
    Destruct A, this addr:0x63fdb8 m_i addr:0
     
    //通过移动构造函数,将对象a2的m_i转移给对象a3;a3的m_i与a2的m_i指向相同的内存地址
    Move Construct A, ori addr:0x63fe08 ori m_i addr:0x776f90 *m_i=1, this addr:0x63fe00 m_i addr:0x776f90 *a.m_i=1
     
    //对象a2析构,由于其资源m_i已转移给对象a3,因此无需释放内存
    Destruct A, this addr:0x63fe08 m_i addr:0
     
    //输出对象a3的信息, a3的m_i指向了最原始的对象a1分配的m_i
    a3 addr:0x63fe00 a3.m_i addr:0x776f90 *a3.m_i=1
     
    //对象a3析构,释放内存
    Destruct A, this addr:0x63fe00 m_i addr:0x776f90
    

    可以看到通过移动构造函数,临时对象的资源可以进行转移,从而减少了不必要的内存分配和释放。

    需要注意的是:

    移动构造函数有一个触发条件,即:当一个对象的构造是基于另一个临时匿名对象时,才能触发。

    int main(){
    	A a3 = getA();    //a3是通过临时对象进行构造,可以触发移动构造函数
    	A a4 = a3;        //a4是通过具名对象a3构造,因此是通过拷贝构造函数来构造
    	return 0;
    }
    
  • 相关阅读:
    收藏CSS经典技巧
    理解这26句话将不再烦恼
    包转发率得计算和背板带宽的计算
    mysql 建表 AUTO_INCREMENT , 数据类型 VARCHAR
    Linux Wine with *.bat *.exe ( Photoshop and etc.. )
    [转载]expect spawn、linux expect 用法小记
    sqlmap.py Database injection and hak
    xls===>csv tables===via python ===> sqlite3.db
    sftp 服务器外网访问设置
    vsftp FTP服务器外网访问设置
  • 原文地址:https://www.cnblogs.com/xiaohai123/p/16473841.html
Copyright © 2020-2023  润新知