我们都知道,在C++中会用一个类变量构造同类变量时会用到拷贝构造函数。对于一般变量,我们在使用过程中并没有什么问题,定义也非常简单,一般我们所使用的都是浅拷贝,其实浅拷贝和深拷贝各有各的好处:
浅拷贝节省空间,但有时会出错,深拷贝更加安全,但有时候会造成不必要的空间浪费。
观察下面函数:
//如果Test类只有一个data值时:
Test(const Test &t)
{
data = t.data;
}
显而易见,这个函数并没有什么错误,也能完成它的功能。但是,我们看下下面的例子,如果用浅拷贝整个程序运行通过不了了:
#include <iostream>
#include <malloc.h>
#include <string.h>
using namespace std;
class String
{
public:
String(const char *str = NULL)
{
cout << "String()!!" << endl;
if(str == NULL){
data = (char *)malloc(sizeof(char));
data[0] = '\0';
}else{
data = (char *)malloc(strlen(str) + 1);
strcpy(data, str);
}
}
String(const String &str)
{
cout << "String(const String &str)!!" << endl;
data = str.data;
}
char *GetData()
{
return this->data;
}
~String()
{
cout << "~String()!!" << endl;
free(data);
}
private:
char *data;
};
int main()
{
String str("Hello"); //构造函数
String str1;
str1 = str; //赋值运算
String str2(str); //拷贝构造
cout << "str = " << str.GetData() << endl;
cout << "str1 = " << str1.GetData() << endl;
cout << "Str2 = " << str2.GetData() << endl;
return 0;
}
运行发现程序core dumped。
很容易发现,程序调用了两次构造函数,一次拷贝构造函数,但析构了两个然后就core dumped了。这就是因为浅拷贝的问题:
在拷贝构造时,我们只是浅拷贝让str1的data指向了str的data空间,虽然是两个不同String变量,但内部data实际上指向的时同一空间,但是程序结束时,str和str1分别调用析构函数来释放它们内部的data值,这样该空间被释放了两次,所以程序运行到最后崩溃。
这里,我们解决的方法是,在String类中增加一个use_count计数值,让它记录指向data的变量个数。如果浅拷贝一次,use_count++,如果要改变某一个变量中的data值,先判断它的use_count值,如果只有它一个指向data时,直接更改,否则,要先对他进行深拷贝同时给use_count-1,然后在对它拷贝来的data值进行修改。析构同理,当use_count减为1时在析构,下面是代码实现:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
using namespace std;
class String;
ostream& operator<<(ostream &out, const String &s);
class String_rep
{
friend class String;
friend ostream& operator<<(ostream &out, const String &s);
public:
String_rep(const char *str = ""):use_count_(0)
{
data = new char[strlen(str) + 1];
strcpy(data, str);
}
String_rep(const String_rep &s);
String_rep& operator=(const String_rep &s)
{
//if(this != &s){
// data = s.rep->data;
//increment();
//}
return *this;
}
~String_rep()
{
delete data;
}
public:
void increment(){++use_count_;}
void decrement()
{
//--use_count_;
if(--use_count_ == 0){
delete this;
}
}
int use_count() const
{
return use_count_;
}
private:
char *data;
int use_count_;
} ;
class String
{
friend ostream& operator<<(ostream &out, const String &s);
public:
String(const char *str = "") : rep(new String_rep(str))
{
rep->increment();
}
String(const String &s)
{
rep = s.rep;
rep->increment();
}
String& operator=(const String &s)
{
if(this != &s){
rep->decrement();
rep = s.rep;
rep->increment();
}
return *this;
}
~String()
{
//int i = this->use_count;
//if((--(rep->use_count_)) == 1){
//free(rep->data);
//delete rep;
//}
rep->decrement();
}
public:
int use_count() const
{
return rep->use_count();
}
void to_upper()
{
char *ch = rep->data;
if(this->use_count() == 1){
while(*ch != '\0'){
*ch -= 32;
ch ++;
}
}else{
(rep->use_count_) --;
rep = new String_rep(rep->data);
strcpy(rep->data, ch);
(rep->use_count_)++;
ch = rep->data;
while(*ch != '\0'){
*ch -= 32;
ch ++;
}
}
}
private:
String_rep *rep;
}s;
ostream& operator<<(ostream &out, const String &s)
{
out << (s.rep)->data;
return out;
}
int main()
{
String s1("abcd");
cout << "s1 = " << s1;
cout << " s1 use_count = " << s1.use_count() << endl;
String s2 = s1;
cout << endl << "s2 = " << s2;
cout << " s2 use_count = " << s2.use_count() << endl;
String s3;
s3 = s1;
cout << endl << "s3 = " << s3;
cout << " s3 use_count = " << s3.use_count() << endl;
s2.to_upper();
cout << endl << "s1 = " << s1;
cout << " s1 use_count = " << s1.use_count() << endl;
cout << "s2 = " << s2;
cout << " s2 use_count = " << s2.use_count() << endl;
cout << "s3 = " << s3;
cout << " s3 use_count = " << s3.use_count() << endl;
}
运行结果;
在拷贝构造时,深拷贝、浅拷贝各有千秋,我们只有恰当的使用它们,才能程序消耗空间尽可能小而且稳定。