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. javascript 高阶函数 实现 AOP 面向切面编程 Aspect Oriented Programming

    AOP的主要作用是吧一些跟核心业务逻辑模块无关的功能 -日志统计, 安全控制, 异常处理- 抽离出来, 再通过"动态织入"的方式掺入业务逻辑模块中. 这里通过扩展Function. ...

  2. mysql 添加远程访问

    第一种:mysql 5.0(两种方式) 一. 在命令窗口输入,mysql -u root -p  回车,并输入密码 执行 use mysql 执行下面句子,查看权限,root默认权限为localhos ...

  3. adb devices如何连逍遥模拟器的设备

    adb device连接真机,上一篇已经讲过了,这篇讲如何连接模拟器.这里我用的模拟器逍遥模拟器.我先插上手机,另外启动了一个模拟器,直接在cmd中输入adb devices,按理应该有2个设备id, ...

  4. SQL Server附加数据库错误5120处理方法

    SQL Server附加数据库5120错误 当我们从另外一台服务器复制过来的数据库,可能会有如下错误: 解决方法 1.给数据库所在文件夹增加用户Everyone并赋予完全控制权限 2.以管理员身份运行 ...

  5. NOIP 模拟五 考试总结

    T1string T1开的不错,看到这个题很激动,类似与HEOI2016排序,好像还要更简单一些,于是迅速冲了个桶排.因为洛谷上排序那道题是用桶排水的,所以我觉得没必要打线段树了,极端大数据20秒冲过 ...

  6. C#开发BIMFACE系列43 服务端API之图纸拆分

    BIMFACE二次开发系列目录     [已更新最新开发文章,点击查看详细] 在上一篇博客<C#开发BIMFACE系列42 服务端API之图纸对比>的最后留了一个问题,在常规业务场景下,一 ...

  7. 洛谷3176 [HAOI2015]数字串拆分 (矩阵乘法+dp)

    qwq真的是一道好题qwq自己做基本是必不可能做出来的. 首先,如果这个题目只是求一个\(f\)数组的话,那就是一道裸题. 首先,根据样例 根据题目描述,我们能发现其实同样数字的不同排列,也是属于不同 ...

  8. NX CAM 读取加工参数

    '取加工几何试图程序组 Function GetGemoGroup_Name(ByVal camObjectTag As NXOpen.Tag) As String Dim theGemoGroupT ...

  9. VS Code Just My Code Debugging

    VS Code Just My Code Debugging VS Code for C++ doesn't support Just My Code Refer here: Add support ...

  10. DL4J实战之六:图形化展示训练过程

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<DL4J实战>系列的第六 ...