C++ 浅拷贝与深拷贝
完整程序见: C++ 浅拷贝 深拷贝
- 没有经过重载,"=" 的作用就是把左边的变量变得和右边的相等,即执行逐个字节拷贝的工作,对于指针变量,会使得两个指针指向同一个地方,这样的拷贝就叫做“浅拷贝”。
- 将一个指针变量指向的内容复制到另一个指针变量指向的地方,这样的拷贝就叫做“深拷贝”。
class CString
{
private:
char* str;
public:
CString() :str(NULL) {}
~CString() { if (str) delete[] str; }
const char* c_str() const { return str; }
CString& operator=(const char* s)
{
char* str_tmp = NULL;
if (s)
{
str_tmp = new char[strlen(s) + 1];
strcpy(str_tmp, s);
}
else
str_tmp = NULL;
if (str)
delete[] str;
str = str_tmp;
return *this;
}
};
按照上面 CString 类的写法,下面的程序片段 "s1 = s2;" 执行的是浅拷贝,会引发问题:
CString s1, s2;
s1 = "this";
s2 = "that";
s1 = s2;
我们可以看到,浅拷贝将导致一系列问题:
- 如不定义自己的赋值运算符,那么 "s1 = s2;" ,实际上导致 s1.str 和 s2.str 指向同一地方。这导致 s1.str 原来指向的那片动态分配的存储空间再也不会被释放,变成内存垃圾,即内存泄漏。
- 此外 s1 和 s2 消亡时都会执行 "delete[] str;" ,这就使得同一片存储空间被释放两次,会导致严重的内存错误,可能会引发程序意外中止。
- 而且,如果执行完 "s1 = s2;" 后又执行 "s1 = "other";" ,会导致 s2.str指向的地方被 delete 释放。
为解决上述问题,需要对 "=" 做再次重载。重载后的 "=" 逻辑,应该是使得执行 "s1 = s2;" 后, s1.str 和 s2.str 依然指向不同的地方,但是这两处地方所储存的内容是一样的。再次重载 "=" 的写法如下:
CString& CString::operator=(const CString& s) // 深拷贝
{
// 为了应对 s=s 这样的情况。其实这里的自赋值判断是多余的,因为下面的写法可以处理自赋值的情况,同时还是异常安全的。
if (this == &s)
return *this;
char* str_tmp = NULL;
if (s.str)
{
str_tmp = new char[strlen(s.str) + 1];
strcpy(str_tmp, s.str);
}
else
str_tmp = NULL;
if (str)
delete[] str;
str = str_tmp;
return *this;
}
程序第3行要判断 this == &s ,是因为要应付这样的情况:s1=s1 。这句 "s1=s1" 本不该改变 s1 的内容才对。
重载了两次的 "=" 的CString类依然可能导致问题,因为没有重写复制构造函数。比如下面的情况:
CString s21;
s21 = "Transformers";
CString s22(s21);
默认复制构造函数执行的是浅拷贝,还会使得 s21.str 和 s22.str 指向同一个地方。因此,还应该为 CString 类编写如下复制构造函数,以完成深拷贝:
CString::CString(const CString& s) // 深拷贝
{
if (s.str)
{
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
}
else
str = NULL;
}