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. 全面分析 Vue 的 computed 和 watch 的区别

    一.computed介绍 computed 用来监控自己定义的变量,该变量在 data 内没有声明,直接在 computed 里面定义,页面上可直接使用. //基础使用 {{msg}} <inp ...

  2. 深入浅出WPF-01.WPF缘起

    WPF缘起 自2012年起,根据公司需要,开始进入界面开发工作.公司是医疗器械行业,很多操作都是基于PC进行的,所以桌面应用开发尤为重要.原有项目都是基于MFC进行的开发,而且是VC6.0的技术,维护 ...

  3. Windows 11 正式版 Build 22000.194 官方简体中文版、英文版(消费者版、商业版)下载

    昨天阿三正式发布了 Windows 11,版本号竟然是 22000.194,也就是 9 月 16 日的 测试版 22000.194,仅仅是文件改了个名,特别是消费者版本 hash 校验都是一致的. W ...

  4. 踩坑系列《一》数据库建表权限 CREATE command denied to user for table

    今天在表中用Navicat连接服务器上的mysql账号进行建表,报了个这样类似的错, CREATE command denied to user for table 是数据库权限设置的问题,所以无法进 ...

  5. mysql从零开始之MySQL 选择数据库

    MySQL 选择数据库 在你连接到 MySQL 数据库后,可能有多个可以操作的数据库,所以你需要选择你要操作的数据库. 从命令提示窗口中选择MySQL数据库 在 mysql> 提示窗口中可以很简 ...

  6. 题解 2020.10.24 考试 T4 模板

    题目传送门 题目大意 有一个 \(n\) 个点组成的树,有 \(m\) 次操作,每次将 \(1\to x\) 的路径上每个点都加入一个颜色为 \(c\) 的小球.但是每个点都有大小限制,即小球个数超过 ...

  7. Java(46)类加载器

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201673.html 博客主页:https://www.cnblogs.com/testero ...

  8. 数字IC设计工程师的知识结构

    刚毕业的时候,我年少轻狂,以为自己已经可以独当一面,庙堂之上所学已经足以应付业界需要.然而在后来的工作过程中,我认识了很多牛人,也从他们身上学到了很多,从中总结了一个IC设计工程师需要具备的知识架构, ...

  9. Mybatis 二级缓存应用 (21)

    [MyBatis 二级缓存] 概述:一级缓存作用域为同一个SqlSession对象,而二级缓存用来解决一级缓存不能夸会话共享,作用范围是namespace级,可以被多个SqlSession共享(只要是 ...

  10. IEEE754浮点数的转换

    将十进制数转换为单精度浮点数 如何将十进制数转换为单精度浮点数参考 首先要知道 IEEE浮点标准:V=(-1)^s * M * 2^E 1.符号(sign)s决定这个数是负数(s=1)还是正数,0(s ...