在c++中定义一个类时,默认会提供一个拷贝构造函数和一个重载的赋值运算符(=)。当我们用一个类对象作为参数初始化另外一个对象时,或者用用赋值运算符将一个对象“赋值”给另一个对象,就会调用拷贝构造函数,例如:
classA A;
classA B(A);//这时将调用拷贝构造函数
classA C=A;//调用拷贝构造函数
当我们用默认的拷贝构造函数初始化对象时候,c++浅拷贝类成员变量。
下面举例说明浅拷贝:
class Cents
{
private:
int m_nCents;
public:
Cents(int nCents=0)
{
m_nCents = nCents;
}
};
但是,一个类中有动态分配的变量时,浅拷贝就会出错。这是因为用赋值运算符拷贝指针变量时仅仅拷贝了指针的地址,它并不为新对象相应指针变量分配空间。看下面这个例子:
class MyString
{
private:
char *m_pchString;
int m_nLength;
public:
MyString(char *pchString="")
{
// Find the length of the string
// Plus one character for a terminator
m_nLength = strlen(pchString) + 1;
// Allocate a buffer equal to this length
m_pchString= new char[m_nLength];
// Copy the parameter into our internal buffer
strncpy(m_pchString, pchString, m_nLength);
// Make sure the string is terminated
m_pchString[m_nLength-1] = '\0';
}
~MyString() // destructor
{
// We need to deallocate our buffer
delete[] m_pchString;
// Set m_pchString to null just in case
m_pchString = 0;
}
char* GetString() { return m_pchString; }
int GetLength() { return m_nLength; }
};
上面这个简单的类定义了一个指针变量,分配了内存来存储我们传入的string。这个类并没有重新定义拷贝构造函数或者重载赋值运算符。因此,c++将进行浅拷贝。注意这几行代码:
MyString cHello("Hello, world!");
{
MyString cCopy = cHello; // 调用默认的拷贝构造函数
} // cCopy 将销毁
std::cout << cHello.GetString() << std::endl; // 程序会崩溃
这段代码看起来没有毛病,其实它会是程序崩溃。下面一句句分析:
MyString cHello("Hello, world!");
这句代码看起来没什么毛病。这句调用了MyStrng类的构造函数,动态分配了内存。然后使cHello.m_pchString指向这段内存。“hello,world”拷贝到这段内存中。
MyString cCopy = cHello; // 调用默认拷贝构造函数
这行看起来也没什么毛病。其实不然,这句执行时,c++调用默认拷贝构造函数即将进行浅拷贝,将cHello.m_pchString这个指针指向的地址拷贝到cCopy.m_pchString中。这样,cCopy.m_pchString和cHello.m_pchString这两个指针指向同样一块内存。
当cCopy对象销毁后,cCopy对象调用MyString类的析构函数。析构函数删除 cCopy.m_pchString 所指向的动态分配的内存,而这块内存恰恰是cHello对象中的m_pchString指针所指向的内存。所以,cCopy对象的销毁会影响cHello对象。
std::cout << cHello.GetString() << std::endl; // 崩溃
这时,将会看到程序崩溃。因为cHello.m_pchString所指向的内存不可用。
造成这个问题的根本原因是调用默认的拷贝构造函数时所用的浅拷贝机制。
下面说说深拷贝
为了解决浅拷贝所造成的问题,需要用深拷贝将动态分配的指针所指向的内容也拷贝一份。这样,新对象和被拷贝对象之间互不影响。下面看用深拷贝修改后的MyString类:
// 拷贝构造函数
MyString::MyString(const MyString& cSource)
{
// m_nLength 并不是动态分配的指针变量, 直接浅拷贝
m_nLength = cSource.m_nLength;
// m_pchString 指针变量,深拷贝之
if (cSource.m_pchString)
{
// 为拷贝分配内存
m_pchString = new char[m_nLength];
// 将string拷贝到新分配的内存中
strncpy(m_pchString, cSource.m_pchString, m_nLength);
}
else
m_pchString = 0;
}
在实际编程时候,为了防止无意中使用默认的拷贝构造函数造成程序崩溃,而又不想深拷贝,我们可以重载拷贝构造函数,函数体为空就可以了。
class MyString
{
private:
char *m_pchString;
int m_nLength;
MyString(const MyString& cSource);
MyString& operator=(const MyString& cSource);
public:
// Rest of code here
};