深拷贝和浅拷贝

来源:互联网 发布:山西大学大数据学院 编辑:程序博客网 时间:2024/06/06 04:31

在c++中定义一个类时,默认会提供一个拷贝构造函数和一个重载的赋值运算符(=)。当我们用一个类对象作为参数初始化另外一个对象时,或者用用赋值运算符将一个对象“赋值”给另一个对象,就会调用拷贝构造函数,例如:

classA A;classA B(A);//这时将调用拷贝构造函数classA C=A;//调用拷贝构造函数

当我们用默认的拷贝构造函数初始化对象时候,c++浅拷贝类成员变量。

下面举例说明浅拷贝:

class Cents{private:    int m_nCents;public:    Cents(int nCents=0)    {        m_nCents = nCents;    }};


当c++对这个类进行浅拷贝时,就是用赋值运算符将成员变量m_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;}


我们需要重载拷贝构造函数。在拷贝构造函数中,m_nLength不是指针变量,所以可以直接用赋值运算符进行赋值。而m_pchString是指针变量,我们再动态分配一块内存用于存放原指针所指向的内容的拷贝。这样,新对象的m_pchString指针和被拷贝对象的m_pchString指针各指一块内存。

在实际编程时候,为了防止无意中使用默认的拷贝构造函数造成程序崩溃,而又不想深拷贝,我们可以重载拷贝构造函数,函数体为空就可以了。

class MyString{private:    char *m_pchString;    int m_nLength;     MyString(const MyString& cSource);    MyString& operator=(const MyString& cSource);public:    // Rest of code here};








0 0
原创粉丝点击