本文将写时拷贝、浅拷贝和深拷贝进行对比。利用string类来进行解释。
一、浅拷贝和深拷贝
1、浅拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
缺陷:浅拷贝,当类里面有指针对象时,拷贝构造和赋值运算符重载都只进行值拷贝(浅拷贝),两个对象指向同一块内存,对象销毁时该空间被释放了两次,因此程序崩溃。释放的时候原来的指针会对该内存块进行释放,当新的指针再进行释放时,它释放的是一个无效的内存块,所以会导致程序崩溃。
浅拷贝的实现非常简单,就是指针间的一个赋值,不会开辟新的内存块。在我们之前c++类中默认函数中提到过拷贝构造和赋值运算符重载函数,系统为我们提供的默认的拷贝构造和赋值运算符重载函数都是浅拷贝实现,在我们有指针对象时,用户可以自实现深拷贝的版本。详见:类中的默认函数
2、深拷贝
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
深拷贝,当类中有指针对象时,一般采用深拷贝就会避免浅拷贝中出现释放无效内存的情况。深拷贝是开辟新的对象内存空间,将原来的内存空间的值复制到新的内存空间中,让新的指针指向新的内存空间。这样它不会原来的指针指向同一对象。对象销毁时,分别调用两次析构,释放的不是同一内存空间,所以不会导致程序崩溃。
缺陷:但深拷贝带来的一个问题就是内存的使用率低。当新的对象指针只是进行读操作时,还要开辟新的空间,新的空间内容与原来的都相同,也不进行修改,这就造成内存浪费。新开的内存就完全没必要。因此我们引入写时拷贝技术来解决这一问题。
二、写时拷贝
写时拷贝是在开始拷贝的时候新旧指针指向同一个对象。当其中的某一个要进行写操作时,再进行新的对象内存的开辟,让要进行写操作的指针指向新开辟的对象内存。写时拷贝技术实际上是一种拖延战术,是为了提高效率而产生的技术。实际上就是在需要开辟空间时,假装开了空间,实际上用的还是原来的空间,减少开辟空间的时间,等到真正要使用新空间的时候才去真正开辟空间。
我们Linux下的fork生成子进程中也会用到。
详情见多进程编程——fork等
自实现利用写时拷贝技术且带有引用计数的string类。
class String
{
public:
String(char* pstr) :_str(new char[strlen(pstr) + 1 + 4])//构造函数
{
_str += 4;
strcpy(_str, pstr);
getRef() = 1;
}
String(const String& rhs) :_str(rhs._str)//构造函数
{
++getRef();
}
String& operator=(const String& rhs)//赋值运算符重载函数
{
if (this != &rhs)
{
Release();
_str = rhs._str;
++getRef();
}
return *this;
}
~String()//析构函数
{
Release();
}
char& operator[](int index)//[]的重载函数
{
if (getRef() > 1)
{
char* ptmp = new char[strlen(_str) + 5];
ptmp += 4;
strcpy(ptmp, _str);
--getRef();
_str = ptmp;
getRef() = 1;
}
return _str[index];
}
private:
void Release()//释放
{
if (--getRef() == 0)
{
delete[] (_str - 4);
}
_str = NULL;
}
int& getRef()//获取引用计数
{
return *((int*)(_str - 4));
}
char* _str;
};
int main()
{
String str1("hello");
String str2(str1);
String str3(str1);
str2[0] = 'w';
char a = str2[0];
return 0;
}