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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)