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;
}