当前位置: 首页 > news >正文

C++11移动构造函数详解

C++11移动构造函数详解

  • 拷贝构造函数
  • 修改后的拷贝构造函数
  • 移动构造函数
  • 移动构造函数的优点

当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。

拷贝构造函数

C++在三种情况下会调用拷贝构造函数(可能有纰漏),第一种情况是函数形实结合时,第二种情况是函数返回时,函数栈区的对象会复制一份到函数的返回去,第三种情况是用一个对象初始化另一个对象时也会调用拷贝构造函数

除了这三种情况下会调用拷贝构造函数,另外如果将一个对象赋值给另一个对象,这个时候回调用重载的赋值运算符函数。

无论是拷贝构造函数,还是重载的赋值运算符函数,我记得当时在上C++课的时候,老师再三强调,一定要注意指针的浅层复制问题。

这里在简单回忆一下拷贝构造函数中的浅层复制问题

首先看一个浅层复制的代码:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>using namespace std;class Str{public:char *value;Str(char s[]){cout<<"调用构造函数..."<<endl;int len = strlen(s);value = new char[len + 1];memset(value,0,len + 1);strcpy(value,s);}Str(Str &v){cout<<"调用拷贝构造函数..."<<endl;this->value = v.value;}~Str(){cout<<"调用析构函数..."<<endl;if(value != NULL)delete[] value;}
};int main()
{char s[] = "I love BIT";Str *a = new Str(s);Str *b = new Str(*a);delete a;cout<<"b对象中的字符串为:"<<b->value<<endl;delete b;return 0;
}

输出结果为:

调用构造函数...
调用拷贝构造函数...
调用析构函数...
b对象中的字符串为:
调用析构函数...

首先结果并不符合预期,我们希望b对象中的字符串也是I love BIT但是输出为空,这是因为b->value和a->value指向了同一片内存区域,当delete a的时候,该内存区域已经被收回,所以再用b->value访问那块内存实际上是不合适的,而且,虽然我运行时程序没有崩溃,但是程序存在崩溃的风险呀,因为当delete b的时候,那块内存区域又被释放了一次,两次释放同一块内存,相当危险呀。

因此对于类中的指针数据成员,必须采用深层复制的方式进行拷贝,深层复制的代码如下:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>using namespace std;class Str{public:char *value;Str(char s[]){cout<<"调用构造函数..."<<endl;int len = strlen(s);value = new char[len + 1];memset(value,0,len + 1);strcpy(value,s);}Str(Str &v){cout<<"调用拷贝构造函数..."<<endl;int len = strlen(v.value);value = new char[len + 1];memset(value,0,len + 1);strcpy(value,v.value);}~Str(){cout<<"调用析构函数..."<<endl;if(value != NULL){delete[] value;value = NULL;}}
};int main()
{char s[] = "I love BIT";Str *a = new Str(s);Str *b = new Str(*a);delete a;cout<<"b对象中的字符串为:"<<b->value<<endl;delete b;return 0;
}

运行结果为:

调用构造函数.. .
调用拷贝构造函数...
调用析构函数...
b对象中的字符串为:l love BIT
调用析构函数...

修改后的拷贝构造函数

有时候我们会遇到这样一种情况,我们用对象a初始化对象b,后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么我们可以对指针进行浅复制,这样就避免了新的空间的分配,大大降低了构造的成本。

但是上面提到,指针的浅层复制是非常危险的呀。没错,确实很危险,而且通过上面的例子,我们也可以看出,浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了。所以我们只要避免第一个指针释放空间就可以了。避免的方法就是将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间(同时也是b->value指向的空间)

所以我们可以把上面的拷贝构造函数的代码修改一下:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>using namespace std;class Str{public:char *value;Str(char s[]){cout<<"调用构造函数..."<<endl;int len = strlen(s);value = new char[len + 1];memset(value,0,len + 1);strcpy(value,s);}Str(Str &v){cout<<"调用拷贝构造函数..."<<endl;this->value = v.value;v.value = NULL;}~Str(){cout<<"调用析构函数..."<<endl;if(value != NULL)delete[] value;}
};int main()
{char s[] = "I love BIT";Str *a = new Str(s);Str *b = new Str(*a);delete a;cout<<"b对象中的字符串为:"<<b->value<<endl;delete b;return 0;
}

运行结果为:

调用构造函数...
调用拷贝构造函数...
调用析构函数...
b对象中的字符串为:l love BIT
调用析构函数...

修改后的拷贝构造函数,采用了浅层复制,但是结果仍能够达到我们想要的效果,关键在于在拷贝构造函数中,最后我们将v.value置为了NULL,这样在析构a的时候,就不会回收a->value指向的内存空间。

这样用a初始化b的过程中,实际上我们就减少了开辟内存,构造成本就降低了。

但要注意,我们这样使用拷贝构造函数有一个前提是:用a初始化b后,a我们就不需要了,最好是初始化完成后就将a析构。如果说,我们用a初始化了b后,仍要对a进行操作,用这种浅层复制的方法就不合适了

所以C++引入了移动构造函数,专门处理这种用a初始化b后就将a析构的情况。

移动构造函数

移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。这意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。移动构造函数的例子如下:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;class Str{public:char *str;Str(char value[]){cout<<"普通构造函数..."<<endl;str = NULL;int len = strlen(value);str = (char *)malloc(len + 1);memset(str,0,len + 1);strcpy(str,value);}Str(const Str &s){cout<<"拷贝构造函数..."<<endl;str = NULL;int len = strlen(s.str);str = (char *)malloc(len + 1);memset(str,0,len + 1);strcpy(str,s.str);}Str(Str &&s){cout<<"移动构造函数..."<<endl;str = NULL;str = s.str;s.str = NULL;}~Str(){cout<<"析构函数"<<endl;if(str != NULL){free(str);str = NULL;}}
};
int main()
{char value[] = "I love zx";Str s(value);vector<Str> vs;//vs.push_back(move(s));vs.push_back(s);cout<<vs[0].str<<endl;if(s.str != NULL)cout<<s.str<<endl;return 0;
}

在此构造函数中,num 指针变量采用的是浅拷贝的复制方式,同时在函数内部重置了 d.num,有效避免了“同一块对空间被释放多次”情况的发生

当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。

使用move()函数,可以将一个左值变成右值,因此a=move(b)调用的是移动构造函数

移动构造函数的优点

移动构造函数是c++11的新特性,移动构造函数传入的参数是一个右值 用&&标出。

首先讲讲拷贝构造函数:拷贝构造函数是先将传入的参数对象进行一次深拷贝,再传给新对象。这就会有一次拷贝对象的开销,并且进行了深拷贝,就需要给对象分配地址空间。而移动构造函数就是为了解决这个拷贝开销而产生的。移动构造函数首先将传递参数的内存地址空间接管,然后将内部所有指针设置为nullptr,并且在原地址上进行新对象的构造,最后调用原对象的的析构函数,这样做既不会产生额外的拷贝开销,也不会给新对象分配内存空间

http://www.lryc.cn/news/108016.html

相关文章:

  • 【力扣】19. 删除链表的倒数第 N 个结点 <链表指针、快慢指针>
  • Vue3和typeScript路由传参
  • SQLserver数据库巡检脚本
  • Go Ethereum源码学习笔记 001 Geth Start
  • idea如何加快创建Maven项目的速度
  • 软件外包开发的GO开发框架
  • oracle会话打满
  • VSCode自定义闪烁光标
  • 复亚智能打造全新云平台:让无人机任务管理更智能、更简单
  • 编程导航第六关——白银挑战
  • 743. 网络延迟时间
  • Kubernetes高可用集群二进制部署(四)部署kubectl和kube-controller-manager、kube-scheduler
  • 在CSDN学Golang场景化解决方案(微服务架构设计)
  • centos7 yum安装mysql5.7
  • 安防视频汇聚平台EasyCVR视频广场面包屑侧边栏支持拖拽操作
  • 爬虫007_python中的输出以及格式化输出_以及输入---python工作笔记025
  • 485modbus转profinet网关连三菱变频器modbus通讯触摸屏监控
  • 话费充值接口文档
  • windows系统的IP、路由、网关、内外网同时访问路由以及修改系统文件hosts的配置
  • Kubespray-offline v2.21.0-1 下载 Kubespray v2.22.1 离线部署 kubernetes v1.25.6
  • 代码随想录训练营Day59单调栈Part01|739. 每日温度|496.下一个更大元素①
  • RPMsg-Lite上手
  • 基于YOLOv8 的 多边形区域内目标检测,跟踪,计数
  • STSP中用于记录节点和旅行回路的四种数据结构
  • 【Spring】AOP切点表达式
  • 设计模式再探——代理模式
  • MySQL日志——查询日志
  • Java版本工程行业管理系统源码-专业的工程管理软件-提供一站式服务 em
  • pytorch的CrossEntropyLoss交叉熵损失函数默认是平均值
  • 【力扣】206. 反转链表 <链表指针>