引言:
問題:在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;
};
總結:
當類中含有指針變量成員時,應選擇深拷貝實現自定義的拷貝構造函數,不能再使用默認的拷貝構造函數,同時,利用引用計數的寫時拷貝對深拷貝進一步優化,如果拷貝構造出的對象不需要寫只需要讀時,就節省了開闢內存與複製數據的開銷。