STL中的string类采用了Copy On Write技术。即通过拷贝构造函数创建的对象不分配新的资源,引用原对象的资源。只有当写操作时,才分配新的资源。如下代码(GCC)演示了通过拷贝构造函数创建的string拥有相同的地址。当改变string的值时,重新分配字符串数组,地址改变。
#include <stdio.h> #include <string> using namespace std; int main() { string s1 = "11111"; string s2 = s1; // s1 and s2 are referring to the same string. printf("%x, %x\n", s1.c_str(), s2.c_str()); // Once a write operation is performed on s1, // s1 will refer to a different string. s1[1] = 'f'; s2[1] = 'v'; printf("%x, %x\n", s1.c_str(), s2.c_str()); return 0; }
以string类为例:
当通过构造函数创建一个string时,new一个字符串数组长度+1的空间,用额外的空间存放引用计数。
当通过拷贝构造函数创建一个string时,让新对象引用原对象,引用计数+1。
当通过赋值操作对一个string进行操作时,原对象引用计数-1,新对象引用计数+1。若原对象引用计数为负,则释放。当前对象引用新对象。
当通过[]操作符对string进行写操作时,对象引用计数-1。new一个新的字符串数组,用来进行写操作。
析构函数需要根据引用计数来判断是否释放资源。
#include <stdio.h> #include <string> #define _CRTDBG_MAP_ALLOC #include "stdlib.h" #include "crtdbg.h" // Copy On Write string class COW { public: COW(const char *c); COW(const COW &c); COW& operator = (const COW &c); char& operator[](int i); ~COW(); const char* c_str() const; private: char *p; }; // Constructor COW::COW(const char *c) { if(NULL != c) { int len = strlen(c); // Use p[len+1] to store the counter. p = new char[len + 2]; strcpy(p, c); // Set the counter to 0; p[len+1] = 0; } } // Destructor COW::~COW() { int len = strlen(p); // Decrease the counter. p[len+1]--; // If there is no object referring to this string, release it. if((p[len+1] < 0) && (p != NULL)) delete[] p; } // Copy constructor COW::COW(const COW &c) { int len = strlen(c.p); // This new object is referring to the source string. this->p = c.p; // Increase the counter. this->p[len+1]++; } // Assignment operator COW& COW::operator = (const COW &c) { if(this != &c) { int len1 = strlen(this->p); int len2 = strlen(c.p); // Decrease the current object's reference counter. this->p[len1+1]--; // Increase the source object's reference counter. c.p[len2+1]++; // If there is no object referring to the original // string, release it. if(this->p[len1+1] < 0) delete[] this->p; // This new object is referring to the source string. this->p = c.p; } return *this; } char& COW::operator[](int i) { static char s = NULL; if(p == NULL) return s; int len = strlen(p); if(i>len) return s; // If the current object is not the only one who is referring to the string, // create a new string to write(copy on write). if(p[len+1] > 0) { p[len+1]--; char *t = new char[len+2]; strcpy(t, p); t[len+1] = 0; p = t; } return p[i]; } // Get the C style string const char* COW::c_str() const { return p; } int main() { // Check for memory leak. _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); COW c1("adsfasdf"); COW c2(c1); COW c3 = c2; // still copy construct. c3 = c2; // assignment // c1, c2 and c3 are referring to the same string. printf("%x, %x, %x\n", c1.c_str(), c2.c_str(), c3.c_str()); // Since c1 is being written, c2 and c3 are refering to the original // string, c1 will refer to a new string. c1[3] = '1'; printf("%x, %x, %x\n", c1.c_str(), c2.c_str(), c3.c_str()); // Since c3 is being written, c2 is refering to the original // string, c3 will refer to a new string. c3[3] = '1'; printf("%x, %x, %x\n", c1.c_str(), c2.c_str(), c3.c_str()); // Since c2 is the only one who is referring to the original // string, the write operation will be on the original string. c2[3] = '1'; printf("%x, %x, %x\n", c1.c_str(), c2.c_str(), c3.c_str()); return 0; }
Copy On Write的好处是当对象通过已有对象构造时,多个对象引用到同一资源。若针对对象的操作都是读操作,可以节省空间。当进行写操作时,再创建新的资源。
Copy On Write也会带来一些问题。若在DLL中返回的对象与当前单元中的对象引用到同一资源,且该资源在DLL中创建。在DLL被动态卸载后,资源被释放。当前单元中的对象所引用的资源则非法。