引言:
问题:在c++中如果没有显式定义拷贝构造函数,编译系统会生成默认的拷贝构造函数,这种机制方便程序员编写程序的同时也为程序员带来了一些麻烦。当类中含有指针成员变量时,默认的拷贝构造函数会将拷贝函数的指针变量值赋给待拷贝构造函数的指针变量,使两个指针变量指向同一片空间,对象销毁时,析构函数就会释放两次动态分配的空间,从而造成错误。
解决方案:
①深拷贝:当类的成员变量含有指针变量时,拷贝构造函数不应该再使用默认的拷贝构造函数,而应重新定义拷贝构造函数(开辟新空间,并将原始数据拷入新开辟的空间)从而避免在对象销毁时产生对同一块空间进行两次释放的错误。
②引用计数的写时拷贝:此种解决方案不必在拷贝构造新对象时开辟新空间哦,而是在需要修改对象的内存空间数据时才开辟新空间并拷贝数据。
深拷贝:
每一次拷贝构造时都直接开辟新的空间,并将数据拷入新的空间。避免销毁对象调用析构函数时重复释放同一片空间。
传统写法:
拷贝构造时自己开辟空间
//深拷贝传统写法
class String
{
public:
String(const char* str)
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
String(const String& s)
{
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
~String()
{
delete[] _str;
_str = NULL;
}
String& operator=(const String& s)
{
if (this != &s)//避免自己给自己赋值
{
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
private:
char* _str;
};
现代写法:
拷贝构造时借助构造函数等创建好内存空间后,交换各自的指针地址就可实现。
//深拷贝现代写法
class String
{
public:
String(const char* str)
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
String(const String& s)
:_str(NULL)
{
String tmp(s._str);
swap(tmp._str, _str);
}
~String()
{
delete[] _str;
_str = NULL;
}
String& operator=(String s)
{
swap(s._str, _str);
return *this;
}
private:
char* _str;
};
引用计数的写时拷贝:
引用计数写时拷贝的好处在于,如果拷贝出来的对象不需要改写只需读的话,就节省了开辟内存和数据复制的开销。
内存连续实现;
//引用计数内存连续实现
class String
{
public:
String(const char* str)
:_str(new char[strlen(str)+5])
{
(*(int*)_str) = 1;
_str = (_str + 4);
strcpy(_str, str);
}
String(const String& s)
:_str(s._str)
{
(*(int*)(_str - 4))++;
}
~String()
{
if ((*(int*)(_str - 4)) == 1)
{
delete[](_str - 4);
_str = NULL;
}
else
{
(*(int*)(_str - 4))--;
_str = NULL;
}
}
String& operator=(String& s)
{
if (s._str != _str)//防止s1 = s1
{
if ((*(int*)(_str - 4)) == 1)//防止同一组相互赋值
{
delete[] (_str - 4);
_str = s._str;
(*(int*)(_str - 4))++;
}
else
{
(*(int*)(_str - 4))--;
_str = s._str;
(*(int*)(_str - 4))++;
}
}
return *this;
}
private:
char* _str;
};
内存分开实现:
//引用计数内存分开实现
class String
{
public:
String(const char* str)
:_str(new char[strlen(str)+1])
, _size(new int(1))
{
strcpy(_str, str);
}
String(const String& s)
:_str(s._str)
, _size(s._size)
{
(*_size)++;
}
~String()
{
if ((*_size) == 1)
{
delete[] _str;
delete[] _size;
_size = NULL;
_str = NULL;
}
else
{
_str = NULL;
(*_size)--;
_size = NULL;
}
}
String& operator=(const String& s)
{
if (_str != s._str)
{
if ((*_size) == 1)
{
delete[] _str;
delete[] _size;
_str = s._str;
_size = s._size;
(*_size)++;
}
else
{
(*_size)--;
_str = s._str;
_size = s._size;
(*_size)++;
}
}
return *this;
}
char& operator[](size_t pos)
{
assert(pos < strlen(_str));
if ((*_size) != 1)
{
char* ptr = _str;
(*_size)--;
_size = new int(1);
_str = new char[strlen(_str) + 1];
strcpy(_str, ptr);
}
return _str[pos];
}
private:
char* _str;
int* _size;
};
总结:
当类中含有指针变量成员时,应选择深拷贝实现自定义的拷贝构造函数,不能再使用默认的拷贝构造函数,同时,利用引用计数的写时拷贝对深拷贝进一步优化,如果拷贝构造出的对象不需要写只需要读时,就节省了开辟内存与复制数据的开销。