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;
}
posted @ 2022-07-13 14:45  萧海~  阅读(202)  评论(0编辑  收藏  举报