假设我们现在有这样一个类,当我们只是为了查询时,实现拷贝构造函数时就可以实现浅拷贝;仅当我们要对生成的对象进行修改时,才进行深拷贝,这样可以很节省空间,也能提高效率。可是如果这样的话,多个浅拷贝,析构时就会导致程序崩溃。因此我们可以自己实现一个析构函数,引入引用计数,当有新的对象生成时,引用计数置为零;之后每拷贝构造产生一个新的对象时,我们使用浅拷贝,引用计数加一;析构函数首先引用计数减一,然后判断引用计数的值是否等于零,如果等于零则delete,否则什么也不做。我们将引用计数设计在内存中,即每次new的时候都多给出四个字节的空间来存放引用计数这个整形数字。放在所有申请的空间的头部。
写时拷贝:对于private成员带有指针的类,如果不想改变其中的值,可以考虑实现浅拷贝,但是这样的话就会在析构的时候出现问题,所以需要一个计数器getRefcount()看有多少个指针指向了同一块内存区域,把析构函数的内部写一个析构减一的函数Release(),检测到有多个指针指向同一个区域时,先减一,判断是否为零,最后析构。如果打算改其中的值,再实现深拷贝,调用计数器对新申请的内存块指针个数重新计数。
由于我们要修改值时一定会调用[],所以我们在[]运算符的重载中实现深拷贝。
class String
{
public:
String(char *p = "") :_str(new char[strlen(p) + 5]()) //此处加五是先加四字节的整型数据再加一个'\0'
{
_str += 4; //表示前四个字节用来存放引用计数,之后的才放其他数据
strcpy(_str, p);
getRefcount() = 1;
}
String(const String& rhs) :_str(rhs._str) //拷贝构造,加引用计数
{
++getRefcount();
}
String& operator=(const String& rhs) //赋值运算符重载,加引用计数
{
if (this != &rhs) //自赋值判断
{
Release(); //this _str
_str = rhs._str;
++getRefcount();
}
return *this;
}
char& operator[](int index) //调用中括号的时候才会出现修改的情况,所以实现中括号的重载时实现深拷贝
{
if (getRefcount() != 1)
{
char* pnewstr = new char[strlen(_str) + 5]();
pnewstr += 4;
strcpy(pnewstr, _str);
--getRefcount();
_str = pnewstr;
getRefcount() = 1;
}
return _str[index];
}
~String()
{
Release();
}
private:
void Release()
{
if (--getRefcount() == 0)
{
delete (_str - 4);
}
_str = NULL;
}
int& getRefcount()
{
return *(int *)(_str - 4); //返回引用计数的值
}
char* _str;
};
int main()
{
String str1("hello");
String str2(str1);
String str3("world");
str3 = str1;
str1[0] = 'a';
return 0;
}
但是按照如上方案会出现一些问题,即我们如果只调用中括号查询,也会进行深拷贝,造成资源不必要的浪费。事实上,我们stl库里面实现的时候也是考虑到这一点,所以不同的stl标准库实现不同, 比如 Centos 6.5 默认的 stl::string 实现就是 『copy-on-write』(写时拷贝), 而 Mac OS X (10.10.5) 实现就是 『eager-copy』(深拷贝)。