C++写时拷贝
一、什么是写时拷贝
如果后续没有对资源进行修改的操作,甚至不会进行数据拷贝,如果在fork函数返回之后,马上调用exec()函数,也不会有数据拷贝
exec函数的作用是让子进程执行其它程序,即替换当前进程的映像
![](https://img2020.cnblogs.com/blog/1468919/202007/1468919-20200724101636134-1057564947.png)
方案一:
如下图所示:
当创建一个对象s1,再拷贝构造一个对象s2时,引用计数_refCount自动加1,此时为2。若此时s2要对自身进行修改,则s2重新开辟一块空间,检查引用计数_refCount是否大于1,若大于1,,则进行修改,这样就不会影响其他对象。
s2重新开辟一块儿空间,再修改s1原来空间的引用计数_refCount,使其减1,重新开辟的空间中因为此时只有s2一个对象,所以引用计数_refCount也为1.
小结:此方案的写时拷贝计数是同时开辟两块空间,一个自身存放的内容,一个存放引用计数_refCount,同时管理两块空间,统计当前使用此空间的对象数,当要修改当前空间的时候,进行对引用计数的判断,再决定是否开辟新的空间。
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class String { public: //构造函数 String(char* str="") :_str(new char[strlen(str)+1]) ,_refCount(new int(1)) { strcpy(_str, str); } //拷贝构造函数 String(String& s) { _str = s._str; _refCount = s._refCount; ++(*_refCount); } String& operator=(const String& s) { if (_str != s._str) { Release(); _str = s._str; _refCount = s._refCount; (*_refCount)++; } return *this; } void Release() { if (--(*_refCount) == 0) { cout << "delete" << endl; delete[] _str; delete _refCount; } } ~String() { Release(); } void CopyOnWrite() { if (*_refCount > 1) { char* tmp = new char[strlen(_str) + 1]; strcpy(tmp, _str); --(*_refCount); _str = tmp; _refCount = new int(1); } } char& operator[](size_t pos) { CopyOnWrite(); return _str[pos]; } char operator[](size_t pos) const { return _str[pos]; } private: char* _str; int* _refCount; };
方案1中是开辟了两块空间进行管理,方案2采用开辟一块空间进行写时拷贝的操作。
开辟一块空间,在这块空间的头4个字节中放置引用计数,真正存放内容的空间从第5个字节开始。一次性多开辟4个字节进行写时拷贝的操作。
具体如下图所示:
当进行操作时,先检查引用计数的个数,然后进行判断是否开辟新的空间,同时修改引用计数的值,防止空间不能释放。
具体例子如下:
当创建了3个对象,s1,s2,s3同时指向一个空间,此时引用计数为3,再创建1个对象s4,s4的引用计数为1。
再进行操作s3 = s4;此时对应的引用计数和对象的指向都需要更改,更改之后如下图所示:
对象s3指向了s4,同时s3原来的空间的引用计数进行减1,新指向空间的引用计数进行加1.
小结:方案2的写时拷贝计数使用一块空间进行内容和引用计数的管理和操作,不开辟两块空间,方便管理。
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class String { public: String(const char* str) :_str(new char[strlen(str) + 5]) { _str += 4; strcpy(_str, str); GetRefCount(); } String(const String& s) :_str(s._str) { ++GetRefCount(); } ~String() { if (--GetRefCount() == 0) { cout << "delete" << endl; delete[](_str - 4); } } String& operator=(const String& s) { if (_str != s._str) { if (--GetRefCount() == 0) { delete[](_str - 4); } _str = s._str; GetRefCount()++; } return *this; } int& GetRefCount() { return *((int*)(_str - 4)); } char& operator[](size_t pos) { CopyOnWrite(); return _str[pos]; } void CopyOnWrite() { if (GetRefCount()>1)//当一块空间有两个或者两个以上的对象指向时,才写时拷贝 { char* tmp = new char[strlen(_str) + 5]; strcpy(tmp, _str); --GetRefCount(); _str = tmp; GetRefCount() = 1; } } private: char* _str; int* _refCount; };
以上转载自https://blog.csdn.net/zy20150613/article/details/76021541
2、数据存储中的写时复制
Linux等的文件管理系统使用了写时复制策略。
举个例子,比如我们有个程序要写文件,不断地根据网络传来的数据写,如果每一次fwrite或是fprintf都要进行一个磁盘的I/O操作的话,都简直就是性能上巨大的损失,
因此通常的做法是,每次写文件操作都写在特定大小的一块内存中(磁盘缓存),只有当我们关闭文件时,才写到磁盘上(这就是为什么如果文件不关闭,所写的东西会丢失的原因)
3、软件应用中的写时复制
在我们经常使用的STL标准模板库中的string类,也是一个具有写时才拷贝技术的类。为了提高性能,STL中的许多类都采用了写时拷贝技术。但是在C++11标准中为了提高并行性取消了这一策略
class String { public: //构造函数(分存内存) String(char* tmp) { _Len = strlen(tmp); _Ptr = new char[_Len + 1 + 1]; strcpy(_Ptr, tmp); // 在数组尾部设置引用计数 _Ptr[_Len + 1] = 0; } //析构函数 ~String() { //引用计数减一 _Ptr[_Len + 1]--; // 引用计数为0时,释放内存 if (_Ptr[_Len + 1] == 0) { delete[] _Ptr; } } //拷贝构造(共享内存) String(string& str) { if (this->_Ptr != str) { //共享内存,.data()返回的是将string的类型转换成char类型的指针 const char *p = str.c_str(); char* pp; strcmp(pp, p); this->_Ptr = pp; this->_Len = str.size(); this->_Ptr[_Len + 1] ++; //引用计数加一 } } //对[]符进行重载,对字符串进行操作的时候,开始写时复制 char& operator[](unsigned int idx) { if (idx > _Len || _Ptr == 0) { static char nullchar = 0; return nullchar; } //引用计数减一 _Ptr[_Len + 1]--; char* tmp = new char[_Len + 1 + 1]; strncpy(tmp, _Ptr, _Len + 1); _Ptr = tmp; // 设置新的共享内存的引用计数 _Ptr[_Len + 1] = 0; return _Ptr[idx]; } private: int _Len; char* _Ptr; };