C++ 写时拷贝 2
什么情况下会用到c++中的拷贝构造函数】:
1)用已经存在的同类的对象去构造出另一个新的对象
2)当函数的形参是类的对象时,这时调用此函数,使用的是值的拷贝,也会调用拷贝构造函数
3)当函数的返回值是类的对象时,这时当函数调用完后,会将函数的对象拷贝构造出一个临时的对象并传给函数的返回处
【浅拷贝】:(位拷贝(值拷贝))
1、概念:所谓的浅拷贝就是当在进行对象的复制时,只是进行对象的数据成员的拷贝,其中默认的拷贝构造函数也是浅拷贝。大多数情况下,使用浅拷贝是没有问题的,但是当出现动态成员,就会出现问题。
2、关于浅拷贝的使用举例:
- #include<iostream>
- using namespace std;
- class Test
- {
- public:
- //构造函数
- Test(int a)
- :_a(a)
- {}
- //拷贝构造函数
- Test(const Test& x)
- {
- _a = x._a;
- }
- private:
- int _a;
- };
- int main()
- {
- Test b(10);
- Test c(b);
- return 0;
- }
3、浅拷贝的缺陷:
浅拷贝对于指针成员不可行。多个对象共用同一块空间,同一内存地址,但是在调用析构函数释放空间的时候,多次调用析构函数,这块空间被释放了多次,此时程序就会崩溃。
【引用计数的拷贝】:
1、(怎么引入的)概念:因为浅拷贝的缺陷,所以在这个时候我们就引入了引用计数的拷贝。
【说明】:引用计数的拷贝是用来解决浅拷贝存在的问题的,所以它也是一种浅拷贝
2、如何实现:我们为每个内存的字符数组添加一个引用计数pcount,即就是在构造函数申请空间的时候多申请出来4个字节。表示有多少个对象使用这块内存,有多少个对象使用,就让pcount值加1,当对象被析构的时候,让pcount值减1,当pcount值为0的时候,将这块内存释放掉。当然pcount也要实现内存共享,所以它也是一个堆中的数据,每个对象都有一个指向它的指针。
3、【说明】:但在此时,pcount的类型的选取,就会要有所考虑?
1)如果选取int类型:(不采取)
- #include<iostream>
- using namespace std;
- class String
- {
- public:
- //构造函数
- String(const char* ptr = "")
- {
- if(ptr == NULL)
- {
- _ptr = new char[1];
- _pcount = 1;
- *_ptr = '\0';
- }
- else
- {
- _pcount = 1;
- _ptr = new char[strlen(ptr)+1];
- strcpy(_ptr,ptr);
- }
- }
- //拷贝构造函数
- String(String& s)
- :_ptr(s._ptr)
- ,_pcount(s._pcount)
- {
- _pcount++;
- }
- //赋值运算符重载
- String& operator=(const String& s)
- {
- if(this != &s)
- {
- if(--_pcount == 0)
- {
- delete[] _ptr;
- //delete _pcount;
- }
- else
- {
- _ptr = s._ptr;
- _pcount = s._pcount;
- (_pcount)++;
- }
- }
- return *this;
- }
- //析构函数
- ~String()
- {
- if((0 == --_pcount) && _ptr!= NULL)
- {
- delete[]_ptr;
- //delete _pcount;
- _ptr = NULL;
- }
- }
- //重载[]
- char& operator[](size_t size)
- {
- if(--_pcount >1)
- {
- char* ptemp = new char[strlen(_ptr)+1];
- int pcount = 1;
- strcpy(ptemp,_ptr);
- _pcount--;
- _ptr = ptemp;
- _pcount = pcount;
- }
- return _ptr[size];
- }
- private:
- char*_ptr;
- int _pcount;
- };
- void FunTest()
- {
- String s1("hello");
- String s2(s1);
- String s3(s2);
- s3 = s2;
- }
- int main()
- {
- FunTest();
- return 0;
- }
调试:
(注意这里我将断点就走到s2,意在说明问题):本来增加s2的时候,两个对象的计数应该是一样的,但是现在一个是1,一个是2,不同步,我们了解到这两个对象的计数变量的地址是不一样的。说明此pcount是公共的,可以被多个对象同时访问。
2)如果选取的是static类型的:(不采取)
- #include<iostream>
- using namespace std;
- class String
- {
- public:
- //构造函数
- String(const char* ptr = "")
- {
- if(ptr == NULL)
- {
- _ptr = new char[1];
- _pcount = 1;
- *_ptr = '\0';
- }
- else
- {
- _pcount = 1;
- _ptr = new char[strlen(ptr)+1];
- strcpy(_ptr,ptr);
- }
- }
- //拷贝构造函数
- String(String& s)
- :_ptr(s._ptr)
- {
- _pcount++; //因为是静态的,所以直接进行计数的增值就可以了
- }
- //赋值运算符重载
- String& operator=(const String& s)
- {
- if(this != &s)
- {
- if(--_pcount == 0)
- {
- delete[] _ptr;
- //delete _pcount;
- }
- else
- {
- _ptr = s._ptr;
- _pcount = s._pcount;
- (_pcount)++;
- }
- }
- return *this;
- }
- //析构函数
- ~String()
- {
- if((0 == --_pcount) && _ptr!= NULL)
- {
- delete[]_ptr;
- //delete _pcount;
- _ptr = NULL;
- }
- }
- //重载[]
- char& operator[](size_t size)
- {
- if(--_pcount >1)
- {
- char* ptemp = new char[strlen(_ptr)+1];
- int pcount = 1;
- strcpy(ptemp,_ptr);
- _pcount--;
- _ptr = ptemp;
- _pcount = pcount;
- }
- return _ptr[size];
- }
- private:
- char*_ptr;
- static int _pcount;
- };
- int String::_pcount = 0;
- void FunTest()
- {
- String s1("hello");
- String s2(s1);
- String s3(s2);
- s3 = s2;
- String s4("world");
- String s5(s4);
- }
- int main()
- {
- FunTest();
- return 0;
- }
调试:
先走到s3,然后走到s4,用s4来构造s5,结果就不对了,走到s4的时候,计数器又变成了1,说明这5个对象公用一个pcount,不能实现引用计数
3)那么我们这样想:如果一个对象第一次开辟空间存放字符串再开辟一块新的空间存放新的额引用计数,当它拷贝构造其它对象时让其它对象的引用计数都指向存放引用计数的同一块空间,pcount设置成int*就可以啦,但是这种方式有缺陷。(读者可以自己实现下)
缺陷一:每次new两块空间,创建多个对象的时候效率比较低
缺陷二:它多次分配小块空间,容易造成内存碎片化,导致分配不出来大块内存
4、代码实现:(所以我将优化版的代码贴出来,其实就是仿照new的底层实现,开辟一块空间,但是它的头几个字节用于计数)
- <span style="font-size:18px;">#include<iostream>
- using namespace std;
- class String
- {
- public:
- String(char *ptr = "")
- {
- if(ptr == NULL)
- {
- _ptr = new char[strlen(ptr)+5];
- _ptr = new char[5];
- *(_ptr+4) = '\0';
- }
- else
- {
- _ptr = new char[strlen(ptr)+5];
- *((int*)_ptr) = 1;
- _ptr += 4;
- strcpy(_ptr,ptr);
- }
- }
- String(const String& s)
- :_ptr(s._ptr)
- {
- (*((int*)(_ptr-4)))++;
- }
- String& operator=(const String& s)
- {
- if(this != &s)
- {
- if(--(*((int*)(_ptr-4))) == 0)
- {
- delete[]_ptr;
- }
- else
- {
- _ptr = s._ptr;
- ++(*(int*)(_ptr-4));
- }
- }
- return *this;
- }
- ~String()
- {
- if((_ptr != NULL) && ((--(*((int*)(_ptr-4)))) == 0))
- {
- delete[]_ptr;
- _ptr = NULL;
- }
- }
- private:
- char* _ptr;
- };
- void Funtest()
- {
- String s1("hello");
- String s2(s1);
- String s3(s2);
- s3 = s2;
- }
- int main()
- {
- Funtest();
- return 0;
- }</span>
【深拷贝】:(址拷贝)
1、概念:采取在堆中申请新的空间来存取数据,这样数据之间相互独立。址拷贝。
2、举例:(string类中的拷贝构造函数)
- <span style="font-size:18px;">String(const String& s)
- {
- _ptr = new char[strlen(s._ptr)+1];
- strcpy(_ptr,s._ptr);
- }</span>
【写时拷贝】:
1、(如何引入的)概念:但是当其中一个对象改变它的值时,其他对象的值就会随之改变,所以此时我们采取这样一种做法,就是写时拷贝。
2、核心思想:
(写入时拷贝)如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),它们会共同获取相同的指针指向的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程中对其他调用者都是透明的。做法的优点:如果调用者没有修改该资源,就不会有副本被创建,因此多个调用者只是读取操作时可以共享同一份资源
(写时拷贝)指用浅拷贝的方法拷贝其他对象,多个指针指向同一块空间,只有当对其中一个对象修改时,才会开辟一个新的空间给这个对象,和它原来指向同一空间的对象不会收到影响。
3、做法:给要改变值的那个对象重新new出一块内存,然后先把之前的引用的字符数据复制到新的字符数组中,这就是写时拷贝。注意,同时还要把之前指向的内存的引用计数减1(因为它指向了新的堆中的字符数组),并在堆中重新new一个块内存,用于保存新的引用计数,同时把新的字符数组的引用计数置为1。因为此时只有一个对象(就是改变值的对象)在使用这个内存。
4、代码实现:
- <span style="font-size:18px;">#include<iostream>
- using namespace std;
- class String
- {
- public:
- //构造函数
- String(const char* ptr = "")
- {
- if(ptr == NULL)
- {
- _ptr = new char[1];
- _pcount = new int(1);
- *_ptr = '\0';
- }
- else
- {
- _pcount = new int(1);
- _ptr = new char[strlen(ptr)+1];
- strcpy(_ptr,ptr);
- }
- }
- //拷贝构造函数
- String(String& s)
- :_ptr(s._ptr)
- ,_pcount(s._pcount)
- {
- (*_pcount)++;
- }
- //赋值运算符重载
- String& operator=(const String& s)
- {
- if(this != &s)
- {
- if(--(*_pcount) == 0)
- {
- delete[] _ptr;
- delete _pcount;
- }
- else
- {
- _ptr = s._ptr;
- _pcount = s._pcount;
- (*_pcount)++;
- }
- }
- return *this;
- }
- //析构函数
- ~String()
- {
- if(0 == --(*_pcount) && _ptr!= NULL)
- {
- delete[]_ptr;
- delete _pcount;
- _ptr = NULL;
- }
- }
- //重载[]
- char& operator[](size_t size)
- {
- if(--(*_pcount) >1)
- {
- char* ptemp = new char[strlen(_ptr)+1];
- int* pcount = new int(1);
- strcpy(ptemp,_ptr);
- (*_pcount)--;
- _ptr = ptemp;
- _pcount = pcount;
- }
- return _ptr[size];
- }
- private:
- char*_ptr;
- int* _pcount;
- };
- void FunTest()
- {
- String s1("hello");
- String s2(s1);
- String s3(s2);
- s3 = s2;
- String s4(s1);
- s1[0] = 'y';
- }
- int main()
- {
- FunTest();
- return 0;
- }</span>
经调试,结果如下:
【注意】:因为不止一个对象使用这一块内存,当修改自己的时,也等于修改了他人的。在向这块存储单元写之前,应该确信没有其他人使用它。如果引用计数大于1,在写之前必须拷贝这块存储单元,这样就不会影响他人了。
C++ 写时拷贝 2相关推荐
- 写时复制,写时拷贝,写时分裂,Copy on write
2019独角兽企业重金招聘Python工程师标准>>> 写时复制,写时拷贝,写时分裂 (Copy-on-write,简称COW)是计算机资源管理方面的一种优化技术,有着广泛的应用,比 ...
- linux进程--写时拷贝技术copy-on-write(七)
COW技术初窥: 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了"写时复制"技术,也就是只 ...
- 17.深浅拷贝和写时拷贝
深浅拷贝 浅拷贝只拷贝指针,但拷贝后两个指针指向同一个内存空间: 深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针指向两个不同地址. 当类成员不包括指针何引用时,浅拷贝并无问题 ...
- php zval_copy_static_var(),zval _ 引用计数 _ 变量分离 _ 写时拷贝
zval.引用计数.变量分离.写时拷贝 我们一步步来理解 1.php语言特性 PHP是脚本语言,所谓脚本语言,就是说PHP并不是独立运行的,要运行PHP代码需要PHP解析器,用户编写的PHP代码最终都 ...
- 写时拷贝(Copy On Write)方案详解
本文旨在通过对 写时拷贝 的四个方案(Copy On Write)分析,让大家明白写时拷贝的实现及原理. 关于浅拷贝与深拷贝,我在之前的博客中已经阐述过了 浅拷贝容易出现指针悬挂的问题,深拷贝效率低 ...
- Linux写时拷贝技术(copy-on-write)
COW技术初窥: 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了"写时复制"技术,也就是只 ...
- 【转】Linux写时拷贝技术(copy-on-write)
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html 源于网上资料 COW技术初窥: 在Linux程序中,fork()会 ...
- Java 写时拷贝容器CopyOnWriteArrayList的测试
测试代码: package copyOnWriteArrayListTest;import java.util.ArrayList; import java.util.List; import jav ...
- 标准C++类std::string的内存共享和Copy-On-Write(写时拷贝)
标准C++类std::string的内存共享,值得体会: 详见大牛:https://www.douban.com/group/topic/19621165/ 顾名思义,内存共享,就是两个乃至更多的对象 ...
- 浅拷贝+引用计数--写时拷贝---模拟实现string容器
引用计数 深拷贝 多个对象共享同一份资源时,最后能够保证该资源只被释放一次 应该由哪个对象释放资源? 由最后一个使用该资源的对象去释放 怎么知道一个对象是最后一个使用该资源的对象? 给一个计数,记录使 ...
最新文章
- opencl track资料整理
- boost::foreach模块一些杂项的测试程序
- java 下载文件选择下载路径_Java后台如何根据路径地址下载文件?
- CentOS安装和配置Rsync进行文件同步
- ML.NET 0.9特性简介
- Linux crontab命令
- druid删除数据_Apache druid 删除数据流程 0.13
- python的email模块_python email 模块
- matlab 连接数组,matlab数组操作知识点总结
- linux gcc g++编译命令选项
- 人工智能的安全问题与差分隐私【笔记】
- 导航猫连接Oracle
- Jmeter压力测试-微信小程序
- 四六级英语听力软件测试,三款精品英语听力软件,提高四六级听力有诀窍
- python英语单词 扇贝英语安卓下载_扇贝单词英语版app下载-扇贝单词英语版安卓版(附修改词汇书教程) - 超好玩...
- 2D人体姿态估计 - Stakced Hourglass Network(SHN)个人理解
- spotify mp3_创建无监督学习的Spotify播放列表
- 关于C++ 里struct 和 class的区别
- 实验吧Web-难-猫抓老鼠
- 快手Java开发二面面经分享
热门文章
- Illustrator、Indesign与Photoshop
- mongodb 如何删除 字段值为 json对象中的某个字段值
- 计算机科学与技术专业《计算机网络原理》课程实验指导书,计算机科学导论,课程实验指导书解读.pdf...
- java menu字体_Java开发网 - 请问如何让菜单字体变宋体?
- android手机最低内存,原神手机端需要哪些配置 手机端最低配置要求介绍
- python concat_python中merge、concat用法
- java中的math.abs_Java.math.BigDecimal.abs()方法
- Win32ASM代码基本模块
- stack.pop()方法_C.示例中的Stack.Pop()方法
- C#Convert.ToInt32(bool)方法-将bool值转换为int