维基百科:
写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
String类中的写时拷贝技术是指用浅拷贝的方法拷贝其他对象,多个指针指向同一块空间,只有当对其中一个对象修改时,才会开辟一个新的空间给这个对象,和它原来指向同一空间的对象不会受到影响。写时拷贝的效率远高于深拷贝。
可以通过增加一个成员变量count来实现写时拷贝,这个变量叫做引用计数,统计这块空间被多少个对象的_str同时指向。当用指向这块空间的对象拷贝一个新的对象出来时count+1,当指向这块空间的一个对象指向别的空间或析构时count-1。只有当count等于0时才可以释放这块空间,否则说明还有其他对象指向这块空间,不能释放。
count应该是什么类型呢?如果是int类型。
class String { public: String(const char* str) :_str(new char[strlen(str)+1]) ,_count(1) { strcpy(_str, str); } String(String& s) :_str(s._str) { ++s._count; _count = s._count; } ~String() { if (--_count == 0) { delete[] _str; } } private: char* _str; int _count; }; void Test() { String s1("aaaaaaaaa"); String s2(s1); }
虽然s1._count和s2._count都等于2,但是当s2执行析构函数后
现在只剩下s1一个对象指向这块空间,s1._count和s2._count应该都变为1,但是s1._count没有改变,查看s1._count和s2._count的地址发现它们并不是同一个地址,改变count只对当前对象有效,其他对象不会受到影响,无法实现引用计数。
这说明count是公共的,可以被多个对象同时访问的。如果是static int类型
class String { public: String(const char* str) :_str(new char[strlen(str)+1]) { _count = 1; strcpy(_str, str); } String(String& s) :_str(s._str) { ++_count; } ~String() { if (--_count == 0) { delete[] _str; } } private: char* _str; static int _count; }; int String::_count = 0; void Test() { String s1("aaaaaaaaa"); String s2(s1); String s3(s2); String s4("bbbbbbbbb"); String s5(s4); }
现在s1、s2、s3的引用计数应该是3,s4、s5的引用计数应该是2。
但是结果不正确。原因是s1、s2、s3指向同一块空间后count增加到3,构造s4时又把count设置为1,s4拷贝构造s5后count增加到2。说明这5个对象共用一个count,不能实现引用计数。
如果一个对象第一次开辟空间存放字符串时再开辟一块新的空间存放引用计数,当它拷贝构造其他对象时让其他对象的引用计数都指向存放引用计数的同一块空间,count设置为int*类型,就可以实现引用计数了。
class String { public: String(const char* str) :_str(new char[strlen(str)+1]) ,_pCount(new int(1)) { strcpy(_str, str); } String(String& s) :_str(s._str) ,_pCount(s._pCount) { ++(*_pCount); } String& operator=(const String& s) { if (/*this != &s ||*/ _str != s._str) //防止自己给自己赋值,或自己拷贝的对象给自己赋值 { //释放原对象 if (--(*_pCount) == 1) { delete _pCount; delete[] _str; } //浅拷贝增加引用计数 _str = s._str; _pCount = s._pCount; ++(*_pCount); } return *this; } ~String() { if (--*_pCount == 0) { delete _pCount; delete[] _str; } } protected: char* _str; int* _pCount; };
但是这种方法也存在不足:
1、它每次new两块空间,创建多个对象时效率较低于下面这种方法;
2、它多次分配小块空间,容易造成内存碎片化,导致分配不出来大块内存。
还有一种方法是在开辟_str时多开辟4个字节,在这块空间的头部保存引用计数。
class String { public: String(const char* str) :_str(new char[strlen(str)+5]) { _str += 4; strcpy(_str, str); //(*(int*)(_str-4)) = 1; _GetRefCount(_str) = 1; } String(const String& s) :_str(s._str) { //*((int*)(_str-4)) += 1; _GetRefCount(_str)++; } String& operator=(const String& s) { if (/*this != &s ||*/ _str != s._str) //防止自己给自己赋值,或自己拷贝的对象给自己赋值 { /*if (--(*(int*)(_str-4)) == 0) { delete[] (_str-4); }*/ _Release(); _str = s._str; ++(*(int*)(s._str-4)); } return *this; } ~String() { /*if (--(*(int*)(_str-4)) == 0) { delete[] (_str-4); }*/ _Release(); } //operator[]的特殊性,读时也拷贝 char& operator[](size_t index) { //当引用计数大于1,需要写时拷贝 if (_GetRefCount(_str) > 1) { char* tmp = new char[strlen(_str) + 5]; --_GetRefCount(_str); //new空间后再减引用计数,防止new空间失败 tmp += 4; _GetRefCount(tmp) = 1; _str = tmp; } return _str[index]; } protected: int& _GetRefCount(char* _ptr) { return *((int*)(_ptr-4)); } //--引用计数,如果引用计数等于0,释放 void _Release() { if (/*--(*(int*)(_str-4))*/--_GetRefCount(_str) == 0) { delete[] (_str-4); } } protected: char* _str; };
void COWTest() { String s1("aaaaaaaaaaa"); String s2(s1); String s3(s1); //operator[]的特殊性,读时也拷贝 cout<<s1[0]<<endl; //写时拷贝 s1[0] = '1'; }
当对s1修改后,s1指向新拷贝出来的空间。
推荐文章: 《C++ STL string的Copy-On-Write技术》
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。