首先我们了解一下什么是写时复制:Scott Meyers推荐我们,在真正需要一个存储空间时才去声明变量(分配内存),这样会得到程序在运行时最小的内存花销,执行到那才会去做分配内存这种比较耗时的工作,这会给我们的程序在运行时有比较好的性能。写时复制(Copy-On- Write)技术,是编程界“懒惰行为”——拖延战术的产物。
以std::string类为例,我们考虑以下问题:
1. 写时复制的原理是什么?
2.string类在什么情况下才共享内存?
3.string类在什么情况下才触发写时复制?
Copy-On-Write一定使用了“引用计数”,必然有一个变量类似于RefCnt; 当第一个string对象str1构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它string对象复制str1时,这个RefCnt会自动加1; 当有对象析构时,这个计数会减1,直到最后一个对象析构时,RefCnt为0,此时,程序才会真正的释放这块从堆上分配的内存。
那么第一个问题来了:RefCnt存放在哪儿呢?
如果存放在string类中,那么每个string的实例都各自拥有自己的RefCnt,根本不能共有一个 RefCnt;
如果是声明成全局变量,或是静态成员,那就是所有的string类共享一个了,这也不行。
结论是我们可以将 size(str) + 1, 最后一位用来存放引用计数,这样对于每一个不同的str实例,都会有相应的引用计数,而且很方面来读写。
第二个问题:string类在什么情况下才共享内存?
1)以一个对象构造自己(复制构造函数)
–只需要在string类的拷贝构造函数中做点处理,让其引用计数累加
2)以一个对象赋值(重载赋值运算符)
第三个问题:string类在什么情况下触发写时才拷贝?
在共享同一块内存的类发生内容改变时,才会发生Copy-On-Write。
接下来用代码展示如何实现引用计数:
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
class String
{
public:
String();
String(const char * pstr);
String(const String & rhs);
String & operator=(const String & rhs);
const char * c_str() const
{
return _pstr;
}
char & operator[](size_t idx);
size_t size() const
{
return strlen(_pstr);
}
size_t refcount() const
{
return _pstr[size() + 1];
}
friend std::ostream & operator<<(std::ostream & os, const String & rhs);
~String();
private:
//在最末尾多加一位用来存放引用计数
void init_refcount()
{
_pstr[size() + 1] = 1;
}
void increaseRefcount()
{
++_pstr[size() + 1];
}
void decreaseRefcount()
{
--_pstr[size() + 1];
}
private:
char * _pstr;
};
String::String()
: _pstr(new char[2]())
{
cout << "String()" << endl;
init_refcount();
}
String::~String()
{
decreaseRefcount();
if(refcount() == 0)
{
delete [] _pstr;
cout << "~String()" << endl;
}
}
String::String(const char * pstr)
: _pstr(new char[strlen(pstr) + 2]())
{
cout << "String(const char *)" << endl;
strcpy(_pstr, pstr);
init_refcount();
}
String::String(const String & rhs)
: _pstr(rhs._pstr)
{
increaseRefcount();
}
String & String::operator=(const String & rhs)
{
if(this != &rhs)
{
decreaseRefcount();
if(refcount() == 0)
{
delete [] _pstr;
cout << "delete lhs._pstr" << endl;
}
_pstr = rhs._pstr;
increaseRefcount();
}
return *this;
}
//[]运算符无法区分读写操作,如何解决?
char & String::operator[](size_t idx)
{
if(idx < size())
{
if(refcount() > 1)
{
decreaseRefcount();
char * ptemp = new char[size() + 2]();
strcpy(ptemp, _pstr);
_pstr = ptemp;
init_refcount();
}
return _pstr[idx];
}
else
{
static char nullchar = '\0';
cout << "下标越界" << endl;
return nullchar;
}
}
std::ostream & operator<<(std::ostream & os, const String & rhs)
{
os << rhs._pstr;
return os;
}
int main()
{
String str;
cout << str << endl;
cout << "str's refcount = " << str.refcount() << endl;
printf("&str = %p\n", str.c_str());
String str2("hello,world");
String str3(str2);
cout << "str2 = " << str2 << endl;
cout << "str3 = " << str3 << endl;
cout << "str2's refcount = " << str2.refcount() << endl;
cout << "str3's refcount = " << str3.refcount() << endl;
printf("&str2 = %p\n", str2.c_str());
printf("&str3 = %p\n", str3.c_str());
cout << endl;
str = str2;//str引用计数为1
cout << "str = " << str << endl;
cout << "str's refcount = " << str.refcount() << endl;
cout << "str2's refcount = " << str2.refcount() << endl;
cout << "str3's refcount = " << str3.refcount() << endl;
printf("&str = %p\n", str.c_str());
printf("&str2 = %p\n", str2.c_str());
printf("&str3 = %p\n", str3.c_str());
cout << "---------" << endl;
String str4 = "wangdao";
String str5(str4);
cout << "str4 = " << str4 << endl;
cout << "str5 = " << str5 << endl;
printf("&str4 = %p\n", str4.c_str());
printf("&str5 = %p\n", str5.c_str());
cout << "str4's refcount = " << str4.refcount() << endl;
cout << "str5's refcount = " << str5.refcount() << endl;
cout << endl;
str4 = str2;//str4引用计数为2
cout << "str = " << str << endl;
cout << "str's refcount = " << str.refcount() << endl;
cout << "str2's refcount = " << str2.refcount() << endl;
cout << "str3's refcount = " << str3.refcount() << endl;
cout << "str4's refcount = " << str4.refcount() << endl;
cout << "str5's refcount = " << str5.refcount() << endl;
printf("&str = %p\n", str.c_str());
printf("&str2 = %p\n", str2.c_str());
printf("&str3 = %p\n", str3.c_str());
printf("&str4 = %p\n", str4.c_str());
printf("&str5 = %p\n", str5.c_str());
cout << endl << "----做写操作" << endl;
str[0] = 'X';
cout << "str = " << str << endl;
cout << "str's refcount = " << str.refcount() << endl;
cout << "str2's refcount = " << str2.refcount() << endl;
cout << "str3's refcount = " << str3.refcount() << endl;
cout << "str4's refcount = " << str4.refcount() << endl;
printf("&str = %p\n", str.c_str());
printf("&str2 = %p\n", str2.c_str());
printf("&str3 = %p\n", str3.c_str());
printf("&str4 = %p\n", str4.c_str());
cout << endl << "---做读操作" << endl;
cout << str2[0] << endl;
cout << "str2 = " << str2 << endl;
cout << "str3 = " << str3 << endl;
cout << "str4 = " << str4 << endl;
cout << "str2's refcount = " << str2.refcount() << endl;
cout << "str3's refcount = " << str3.refcount() << endl;
cout << "str4's refcount = " << str4.refcount() << endl;
printf("&str2 = %p\n", str2.c_str());
printf("&str3 = %p\n", str3.c_str());
printf("&str4 = %p\n", str4.c_str());
return 0;
}