【C++】c++写时拷贝Copy On Write
2015-10-03 23:09
453 查看
Copy On Write
Copy On Write(写时复制)使用了“引用计数”(reference counting),会有一个变量用于保存引用的数量。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数为1或是0。此时,程序才会真正的Free这块从堆上分配的内存。
写时复制(Copy-On-Write)技术,就是编程界“懒惰行为”——拖延战术的产物。举个例子,比如我们有个程序要写文件,不断地根据网络传来的数据写,如果每一次fwrite或是fprintf都要进行一个磁盘的I/O操 作的话,都简直就是性能上巨大的损失,因此通常的做法是,每次写文件操作都写在特定大小的一块内存中(磁盘缓存),只有当我们关闭文件时,才写到磁盘上 (这就是为什么如果文件不关闭,所写的东西会丢失的原因)。
class String { public: String(char* ptr = "") //构造函数 :_ptr(new char[strlen(ptr)+1]) { strcpy(_ptr, ptr); } String(const String& s) :_ptr(new char[strlen(s._ptr)+1])//另外开辟空间 { strcpy(_ptr, s._ptr); } ~String() { if (_ptr) { delete[] _ptr; } } private: char* _ptr; };
void Test() { String s1 = "hello world"; int begin = GetTickCount();//记录此时毫秒数 for (int i = 0; i < 10000; ++i) { String s2 = s1; } int end = GetTickCount();//记录此时毫秒数 cout << "cost time:" << end - begin << endl; }
GetTickCount : 在Release版本中,该函数从0开始计时,返回自设备启动后的毫秒数(不含系统暂停时间)。在头文件windows.h中。
在上面for循环中,语句“String s2 = s1;”不断调用拷贝构造函数为s2开辟空间,执行完语句“String s2 = s1;”后,不断调用析构函数对s2进行释放,导致低效率,Test执行结果如下图:
写时拷贝~~写时拷贝~自然是我们自己想写的时候再进行拷贝(复制),下面引入几种方案如下:(试着判断哪一种方案可行)
这里又引入另外一个概念“引用计数”:string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时(即其它对象也指向这块内存),这个计数为自动累加,上面方案中的_retCount就是用来计数的。
简单地介绍一下上面三个方案。方案一和方案二是不可行的,方案一中的_retCount是属于每个对象内部的成员,当有多个对象同时指向同一块空间时,_retCount无法记录多个对象;方案二中的_retCount是静态成员变量,是所有对象所共有,似乎可以记录,举个例子:对象s1、s2指向A空间,_retCount为2,对象s3、s4指向B空间,此时_retCount变为4,但是当想释放B空间时,应当在析构函数中_retCount减到0时释放,但是当_retCount减到0时,却发现释放的是A空间,而B空间发生了内存泄露。也就是静态成员变量_retCount只能记录一块空间的对象个数。
- 下面通过代码介绍方案三:
class String { public: String(char* ptr = "") //构造函数 :_ptr(new char[strlen(ptr)+1]) , _retCount(new int(1))//每个对象对应一个整型空间存放 { //指向这块空间的对象个数 strcpy(_ptr, ptr); } String(const String& s) //拷贝构造函数 :_ptr(s._ptr) , _retCount(s._retCount) { _retCount[0]++; } String& operator= (const String& s) //赋值运算符重载 { if (this != &s) { if (--_retCount[0] == 0) {//旧的引用计数减1,如果是最后一个引用对象,则释放对象 delete[] _ptr; delete[] _retCount; } _ptr = s._ptr;//改变this的指向,并增加引用计数 _retCount = s._retCount; ++_retCount[0]; } return *this; } ~String() { if (--_retCount[0] == 0) { delete[] _ptr; delete[] _retCount; } } private: char* _ptr; int* _retCount; };
同样执行Test函数,测试结果如下图:
下面进一步优化方案三来介绍写时拷贝(写时复制)
方案三:是每个对象对应一个整型空间(即_refCount)存放指向这块空间的对象个数
再优化:不引用_refCount,但每次给_ptr开辟空间的时候,多开辟四个字节,用来记录指向此空间的对象个数,规定用开头那四个字节来计数。
class String { public: String(char* ptr = "") :_ptr(new char[strlen(ptr)+5]) { _ptr += 4; strcpy(_ptr,ptr); _GetRefCount(_ptr) = 1;//每构造一个对象,头四个字节存放计数 } String(const String& s) :_ptr(s._ptr) { _GetRefCount(_ptr)++; //每增加一个对象,引用计数加1 } String& operator= (const String& s) { if (this != &s) { Release(_ptr); _ptr = s._ptr; _GetRefCount(_ptr)++; } return *this; } char& operator [](size_t index) { if (_GetRefCount(_ptr) > 1) { --_GetRefCount(_ptr);//旧引用计数减1 char* str = new char[strlen(_ptr) + 1];//另外开辟一个空间 str += 4; strcpy(str, _ptr); _GetRefCount(str) = 1; _ptr = str; } } ~String() { Release(_ptr); } inline void Release(char* ptr) { if (--_GetRefCount(ptr) == 0) { delete[](ptr - 4); } } inline int& _GetRefCount(char* ptr) { return *(int*)(ptr - 4);//访问头四个字节 } private: char* _ptr; };
程序执行过程,看下图说话
对下列函数进行解析:
char& operator [](size_t index) { if (_GetRefCount(_ptr) > 1) { --_GetRefCount(_ptr);//旧引用计数减1 char* str = new char[strlen(_ptr) + 1];//另外开辟一个空间 str += 4; strcpy(str, _ptr); _GetRefCount(str) = 1; _ptr = str; } }
当在主函数中执行语句:s1[0] = ‘w’;时,想要改变s1对象中_ptr[0]的值;但是当我们改变s1中_ptr[0]的值时,不希望把s2、s3中_ptr[0]的值也改变了。由于s1、s2、s3目前指向同一块空间,改变其中一个,另外两个肯定也跟着改变了,所以提供了另外一种方法:把对象s1分离出来,旧引用计数减1,另外给s1开辟一段跟原来一样的空间,存放一样的内容,这时候即使改变了s1的内容,也不影响s2、s3的对容。
一样看下图说话:
相关文章推荐
- C语言——接口设计原则
- 《C++primer(第五版)》学习之路-第十六章:模板与泛型编程
- 从RTTI谈C++的向下转型
- C/C++求非波拉契排列第N项
- c++11支持类数据成员的初始化
- C++ static、const和static const 以及它们的初始化
- C打印函数printf的一种实现原理简要分析
- C++ - string 基本版
- C++ Primer 第三章 标准库类型习题解答
- C语言实现密码输入
- c语言面试之字符串
- 链表002
- C++ 内存分配(new,operator new)详解
- 项目32.6 输出小星星
- C++primer第五版笔记-第八章IO库
- 项目32.5 输出小星星
- Microsoft Visual C++ 6.0快捷键(绝对值得掌握)
- C++primer读书笔记9-转换和类类型
- C++ 基础知识点 二 第3章 C++程序的流程控制
- 【C疯狂的教材】(九)C语言指针(一)