C++类默认拷贝构造函数的弊端

C++类的中有两个特殊的构造函数,(1)无参构造函数,(2)拷贝构造函数。它们的特殊之处在于:

(1) 当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数且其函数体为空;

(2) 当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝。(这个拷贝操作是浅拷贝)

这里只讲拷贝构造函数。在C语言中,

int a = 5;  //初始化
int b;
b = 6; //赋值

上面的初始化及赋值操作是最正常不过的语法,C++语言肩挑兼容C语言语法的责任,所以在类的设计上,也兼容这种操作:

class cls
{
pubic:
//...
} int main(void)
{
cls c1;
cls c2 = c1; //初始化类,还可以 cls c2(c1);
cls c3; c3 = c1; //赋值类 //... return 0;
}

如上的初始化类需要调用到cls类的默认实现的拷贝构造函数,为类赋值需要调用的是cls类的默认实现的赋值操作符重载函数,它们都是浅度拷贝的。前者其原型为:

cls(const cls& c)

默认的拷贝构造函数存在弊端,看如下类定义:

class TestCls{
public:
int a;
int *p; public:
TestCls() //无参构造函数
{
std::cout<<"TestCls()"<<std::endl;
p = new int;
} ~TestCls() //析构函数
{
delete p;
std::cout<<"~TestCls()"<<std::endl;
}
};

类中的指针p在构造函数中分配的空间,在析构函数中释放。

int main(void)
{
TestCls t; return 0;
}

编译运行确实不会出错:

类在我们没有定义拷贝构造函数的时候,会默认定义默认拷贝构造函数,也就是说可以直接用同类型的类间可以相互赋值、初始化:

int main(void)
{
TestCls t1;
TestCls t2 = t1; //效果等同于TestCls t2(t1); return 0;
}

编译通过,运行却出错了:

原因就在于,默认的拷贝构造函数实现的是浅拷贝

深度拷贝和浅拷贝

深度拷贝和浅拷贝在c语言中就经常遇到的了,在这里我简单描述。

一般的赋值操作是深度拷贝:

//深度拷贝
int a = 5;
int b = a;

简单的指针指向,则是浅拷贝:

//浅拷贝
int a = 8;
int *p;
p = &a; char* str1 = "HelloWorld";
char* str2 = str1;

将上面的浅拷贝改为深度拷贝后:

//深度拷贝
int a = 8;
int *p = new int;
*p = a; char* str1 = "HelloWorld";
int len = strlen(str1);
char *str2 = new char[len];
memcpy(str2, str1, len);

总而言之,拷贝者和被拷贝者若是同一个地址,则为浅拷贝,反之为深拷贝。

例:以字符串拷贝为例,浅拷贝后,str1和str2同指向0x123456,不管哪一个指针,对该空间内容的修改都会影响另一个指针。

深拷贝后,str1和str2指向不同的内存空间,各自的空间的内容一样。因为空间不同,所以不管哪一个指针,对该空间内容的修改都不会影响另一个指针。

解决默认拷贝构造函数的弊端

类的默认拷贝构造函数只会用被拷贝类的成员的值为拷贝类简单初始化,也就是说二者的p指针指向的内存空间是一致的。以前面TestCls可以知道,编译器为我们默认定义的拷贝构造函数为:

TestCls(const TestCls& testCls)
{
a = testCls.a;
p = testCls.p; //两个类的p指针指向的地址一致。
}

解释:main函数将要退出时,拷贝类t2的析构函数先得到执行,它把自身p指向的堆空间释放了;接下来,t1的析构函数得到调用,被拷贝类t1的析构函数得到调用,它同样要去析构自身的p指向指向的堆空间,但是该空间和t2类中p指向的空间一样,造成重复释放,程序运行崩溃。

解决办法十分简单,自定义拷贝构造函数,里面用深度拷贝的方式为拷贝类初始化:

class TestCls{
public:
int a;
int *p; public:
TestCls()
{
std::cout<<"TestCls()"<<std::endl;
p = new int;
} TestCls(const TestCls& testCls)
{
std::cout<<"TestCls(const TestCls& testCls)"<<std::endl;
a = testCls.a;
//p = testCls.p;
p = new int; *p = *(testCls.p); //为拷贝类的p指针分配空间,实现深度拷贝
} ~TestCls()
{
delete p;
std::cout<<"~TestCls()"<<std::endl;
}
}; int main(void)
{
TestCls t1;
TestCls t2 = t1; return 0;
}

编译运行正常:

关于c++拷贝构造函数的深度拷贝和浅拷贝的介绍到这里,其实还可以将它们的地址打印出来看看,不过这一步就不再赘述了。

拷贝构造函数其它妙用

自定义拷贝构造函数,并设置为private属性,其实现体可以什么都不写,那么这个类将变成一个不可被复制的类了。

[参考博文: https://blog.csdn.net/qq_29344757/article/details/76037255]

C++ 默认拷贝构造函数 深度拷贝和浅拷贝的更多相关文章

  1. C++拷贝构造函数(深拷贝,浅拷贝)

    对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. #i ...

  2. C++拷贝构造函数(深拷贝,浅拷贝)

    http://www.cnblogs.com/BlueTzar/articles/1223313.html C++拷贝构造函数(深拷贝,浅拷贝) 对于普通类型的对象来说,它们之间的复制是很简单的,例如 ...

  3. c++ 拷贝构造函数、拷贝运算符、析构函数

    拷贝构造函数.拷贝运算符.析构函数 拷贝构造函数.拷贝运算符.析构函数 定义行为像值的类 class HasPtr{ public: HasPtr(const string &s = stri ...

  4. Python [拷贝copy / 深度拷贝deepcopy] | 可视化理解

    Python 是一门面向对象的语言, 在Python一切皆对象. 每一个对象都有由以下三个属性组成: ------------------------------------------------- ...

  5. 【转】C++的拷贝构造函数深度解读,值得一看

    建议看原帖  地址:http://blog.csdn.net/lwbeyond/article/details/6202256 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很 ...

  6. C++拷贝构造函数(深拷贝与浅拷贝)

    转自http://blog.csdn.net/lwbeyond/article/details/6202256/ 一. 什么是拷贝构造函数 对于普通类型的对象来说,它们之间的复制是很简单的,例如:in ...

  7. C++之拷贝构造函数、深拷贝、浅拷贝

     C++ Code  12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...

  8. c++拷贝构造函数,深拷贝,浅拷贝,对象内存

    https://blog.csdn.net/lwbeyond/article/details/6202256 防止默认拷贝发生 通过对对象复制的分析,我们发现对象的复制大多在进行“值传递”时发生,这里 ...

  9. C++ 拷贝构造函数、拷贝赋值运算符、析构函数

    每一次都会忘,做个笔记吧.想到哪里写到哪里. 拷贝构造函数 第一个参数必须是自身类类型的引用,且任何额外参数都有默认值.(为什么必须是引用?见后解释) 合成拷贝构造函数:如果我们没有为一个类定义拷贝构 ...

随机推荐

  1. P4338-[ZJOI2018]历史【LCT】

    正题 题目链接:https://www.luogu.com.cn/problem/P4338 题目大意 给出\(n\)个点的一棵树,和每个点进行\(access\)的次数\(a_i\),要求安排一个顺 ...

  2. mysql8.0.20安装教程,mysql下载安装教程8.0.20

    mysql8.0.20下载安装教程  mysql8.0.20安装教程 mysql安装包+mysql学习视频+mysql面试指南视频教程 下载地址: 链接:https://pan.baidu.com/s ...

  3. 从零搭建基于webpack的Electron-Vue3项目(1)——基于webpack的Vue3项目搭建

    从零搭建基于webpack的Electron-Vue3项目(1)--基于webpack的Vue3项目搭建 前言 本篇文章内容,主要是基于webpack的Vue3项目开发环境进行搭建,暂时还不涉及到El ...

  4. 题解 CF736D Permutations

    link Description 现在,你有一个二分图,点数为 \(2n\). 已知这个二分图的完备匹配的个数是奇数. 现在你要知道,删除每条边后,完备匹配个数是奇数还是偶数. \(1\le n\le ...

  5. appium+Andriod环境搭建遇到问题

    报错:Caused by: org.openqa.selenium.WebDriverException: An unknown server-side error occurred while pr ...

  6. Fastjson中getJSONObject()与getJSONArray()的使用

    测试JSON串: { "package": { "List1": { "errorCode": "0", "e ...

  7. NX Open 切削层加载

    UF_CUT_LEVELS_load Loads the current cut levels for the specified operation into the data structure ...

  8. RabbitMQ:从入门到搞定面试官

    安装 使用docker安装,注意要安装tag后缀为management的镜像(包含web管理插件),我这里使用的是rabbitmq:3.8-management 1. 拉取镜像 shell docke ...

  9. 痞子衡嵌入式:超级下载算法RT-UFL v1.0在Keil MDK下的使用

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...

  10. 服务器端的GPU使用

    服务器端的GPU使用 查看GPU信息 查看nvidia GPU信息: # 输入指令 lspci | grep -i nvidia # 结果如下: # 04:00.0 3D controller: NV ...