我们在写代码的时候经常会用到拷贝,但是有时候默认的拷贝方式就会产生致命的错误。比如我们定义的类的内部十分复杂,如果用编译器给我们提供的默认拷贝构造函数就会出现错误。我们应该知道何时我们需要自己手动写拷贝构造函数。
一、深拷贝和浅拷贝
浅拷贝:只是拷贝了指针变量本身,但是没有把它指向的内存重新分配空间。
深拷贝:不仅拷贝了指针变量,而且把其所指向的内存重新分配了一次内存空间。
我们来看一个例子比较这两种拷贝带来的不同后果
#include <iostream> using namespace std; class Test { public: Test(const char * myp); //带一个参数的构造函数 ~Test(); //构造函数 private: char * m_p; int m_len; }; Test::Test(const char * myp) { m_len = strlen(myp); m_p = new char[m_len + 1]; //给p申请一段内存空间。 strcpy(m_p, myp); cout << "构造函数完成" << endl; cout << m_len << endl; } Test::~Test() { if (m_p != NULL) { delete [] m_p;//释放p所指向的那一段内存空间。 m_p = NULL; m_len = 0; } cout << "析构函数完成" << endl; } void display() //用来显示Test类对象的完整生命周期 { Test t1("abc"); Test t2 = t1; // 这个时候会调用拷贝构造函数。 } int main() { display(); system("pause"); return 0; }
我们在display()函数中定义了一个类的对象t1并且用“abc”初始化了t1中的m_p成员。然后我们又声明了一个t2对象,打算用t1来初始化它,这个时候会调用一个拷贝构造函数,但是我们没有写拷贝构造函数,所以编译器会给我们提供一个默认的拷贝构造函数,这个拷贝函数执行的就是一个浅拷贝的操作。只是把t1对象中的m_p指针变量拷贝给t2中的m_p成员,但是没有给t2中的m_p成员重新分配一次内存空间,所以这两个指针会指向同一个内存空间,等到display()函数结束就会调用析构函数,把t2先析构,然后析构t1,因为两个对象中的指针都指向同一个内存空间,所以这个内存将会被释放两次,这就会使我们的程序崩溃。
二、解决浅拷贝带来的问题
上面的问题是由于我们使用了编译器给我们提供的默认拷贝构造函数,默认的构造函数和使用=运算符都是使用的浅拷贝,只要我们自己写拷贝构造函数就可以解决浅拷贝带来的问题了。
我们只要加上这样一个拷贝构造函数就可以了。
Test::Test(const Test& obj) { m_len = obj.m_len; m_p = new char[m_len + 1]; //重新给m_p分配了一段内存空间。 strcpy(m_p, obj.m_p); cout << "拷贝构造函数完成" << endl; cout << m_len << endl; }
从上面的拷贝构造函数可以知道,我们不仅拷贝了指针变量本身,而且给它分配了一段内存空间,这样两个指针就不是指向同一块内存了。这样调用析构函数的时候就不会把同一块内存释放两次了。
当然在使用赋值运算符=的时候,编译器默认的也是浅拷贝,在需要的时候我们也要重载=运算符。运算符重载会在下一篇博客写出来。