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. element-ui的Tree树组件使用技巧

    目录 1,前言 2,需求 3,解决思路 4,完整代码 5,总结 1,前言 最近这段时间在做一个新的模块,其中有一个三层的树结构,产品经理提出了一个很古怪的需求,整的我只能自己控制树的交互,写完之后,感 ...

  2. jQuery has been removed

    jQuery has been removed, 新的项目不要用jQuery了 这些问题都已经有了解决方案 * $()选择器, * $.ajax, * $dom.on("click" ...

  3. libcurl 概述(翻译)

    l名称 libcurl 客户端 URL 传输 描述 这是一个如何使用libcurl进行C语言编程的简短概述.这里提到的每个功能都有特定的手册页.还有 libcurl-easy 手册页,libcurl- ...

  4. P1251-餐巾计划问题【费用流】

    正题 题目链接:https://www.luogu.com.cn/problem/P1251 题目大意 \(N\)天,第\(i\)天需要\(a_i\)个餐巾. 每个餐巾价格为\(p\),使用完后有两种 ...

  5. 如何把springboot项目部署到tomcat上

    前言: 开始以为打包springboot项目为war包丢到tomcat上的webapps下面就可以访问controller层的路径了,可是调用接口却报404的错误,而打开8080的主页,不加路径却可以 ...

  6. 05-IdentityServer4

    前面我们认识了jwt的token颁发模式,其中的应用场景和部分缺陷已经很是了解了.有些场合并不适合jwt,特别是针对第三方进行使用时,比如我们使用qq或者微信登陆博客园或其他第三方应用时. Ids4的 ...

  7. Ubuntu安装Oracleclient远程连接数据库

    平时Oracle数据库都安装在本地或者WindowsServer上进行使用,但因为工作需要,领导要求在虚拟机中安装Ubuntu来连接Windows本地安装的数据库,先将操作过程和遇到的问题进行梳理总结 ...

  8. Chrome安装Postman以及启动的方式

    Postman一个web开发人员必不可少的接口调试神器 Chrome安装Postman的方法网上很多,就不一一列举了我个人使用的方式目前常用的两种方式 方式一:下载插件安装包使用开发者模式安装 推荐一 ...

  9. Zookeeper原理系列-Paxos协议的原理和Zookeeper中的应用分析

    Paxo算法介绍 Paxos算法是莱斯利·兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法. Paxos产生背景 Paxos算法是基于消息传递且具有高度容错特性的一致 ...

  10. Docker小白到实战之Docker Compose在手,一键足矣

    前言 Docker可以将应用程序及环境很方便的以容器的形式启动,但当应用程序依赖的服务比较多,或是遇到一个大系统拆分的服务很多时,如果还一个一个的根据镜像启动容器,那就有点累人了,到这有很多小伙伴会说 ...