在C++的类中,拷贝构造函数是非常有用的。
为便于调试和查看内存状态,本代码使用VS2015来调试。
1.默认拷贝构造函数
有下面一段代码:
1 #include "stdafx.h" 2 #include <iostream> 3 4 using namespace std; 5 6 class A 7 { 8 public: 9 int n; 10 char c; 11 int data[10]; 12 protected: 13 private: 14 }; 15 16 17 int _tmain(int argc, _TCHAR* argv[]) 18 { 19 A a; 20 for (int i = 0; i < 10; i++) 21 { 22 a.data[i] = i; 23 } 24 a.n = 123; 25 a.c = 'A'; 26 27 A b = a; 28 29 return 0; 30 }
在这段代码中,声明一个类A,定义一个A的对象a,再定义一个A的对象b,我们想让b与a相等。将断点设置到第29行,得到下面的结果。
对象a和b的变量n,c和数组data的值完全一致,且a.data与b.data的地址不相同,这正是我们期待的结果。
在这段代码中,我们并没有写有关拷贝构造函数的代码,C++会根据它内部的规则为我们隐式创建一个拷贝构造函数,我们管它叫做默认拷贝构造函数。但是很多时候我们并不能依赖默认的东西,看下边的例子。
2.默认拷贝构造函数的局限性
有下面一段代码(这是一段错误的代码):
1 #include "stdafx.h" 2 #include <iostream> 3 4 using namespace std; 5 6 class Matrix 7 { 8 public: 9 Matrix() 10 { 11 pData = NULL; 12 } 13 ~Matrix() 14 { 15 if (pData) 16 { 17 delete[] pData; 18 pData = NULL; 19 } 20 } 21 int w; 22 int h; 23 int* pData; 24 protected: 25 private: 26 }; 27 28 int main() 29 { 30 Matrix a; 31 a.w = 3; 32 a.h = 3; 33 int size = a.w*a.h; 34 a.pData = new int[size]; 35 for (int i = 0; i < size; i++) 36 { 37 a.pData[i] = i; 38 } 39 40 Matrix b = a; 41 42 return 0; 43 }
在这段代码中,声明一个矩阵类Matrix,因为矩阵的大小是不一定的,所以只能定义指针动态申请内存了。定义一个Matrix的对象a,再定义一个Matrix的对象b,我们想让b与a相等。将断点设置到第29行,得到下面的结果。
此时对象a和b的w,h和pData的地址是相同的。所以这是一段有问题的代码,因为这段代码并不能正确结束。因为当main()函数结束时,会先调用b的析构函数,此时b.pData已被释放,析构完b之后,紧接着析构a,释放a.pData内存时,就会出问题。
当然有些人可能会觉得“我不这么写就可以了”,那么我们看下面这段代码(这是一段错误的代码)。
1 #include "stdafx.h" 2 #include <iostream> 3 #include <list> 4 5 using namespace std; 6 7 class Matrix 8 { 9 public: 10 Matrix() 11 { 12 pData = NULL; 13 } 14 ~Matrix() 15 { 16 if (pData) 17 { 18 delete[] pData; 19 pData = NULL; 20 } 21 } 22 int w; 23 int h; 24 int* pData; 25 protected: 26 private: 27 }; 28 29 void CreateMatrixList(list<Matrix>& myList) 30 { 31 Matrix a; 32 a.w = 3; 33 a.h = 3; 34 int size = a.w*a.h; 35 a.pData = new int[size]; 36 for (int i = 0; i < size; i++) 37 { 38 a.pData[i] = i; 39 } 40 myList.push_back(a); 41 } 42 43 int main() 44 { 45 list<Matrix> aList; 46 CreateMatrixList(aList); 47 48 auto pNode = aList.begin(); 49 int m = (*pNode).pData[2]; 50 51 return 0; 52 }
在这段代码中,我想创建一个矩阵链表,在第51行设置断点,得到如下结果。
此时m的值并不是我们之前设置的2,而是一个不确定的数。我们在16行,40行,51行都设置断点进行调试。测得断点的停留顺序为40,16,51,16。也就是说析构函数走了两次,这说明在CreateMatrixList函数中,myList中的矩阵,并不是刚刚定义好的a,那只是一个副本,a已经被默默的干掉了。
这个问题也纠结了我很长时间,为什么我的list对别人的类进行push_back都没什么问题,对我自己的类就行。后来我知道就是默认拷贝构造函数惹的祸。
3.拷贝构造函数
在上面的代码中,增加拷贝构造函数Matrix(const Matrix& m),如下:
1 #include "stdafx.h" 2 #include <iostream> 3 #include <list> 4 5 using namespace std; 6 7 class Matrix 8 { 9 public: 10 Matrix() 11 { 12 pData = NULL; 13 } 14 Matrix(const Matrix& m) 15 { 16 w = m.w; 17 h = m.h; 18 if (m.pData) 19 { 20 pData = new int[w*h]; 21 memcpy(pData, m.pData, w*h * sizeof(int)); 22 } 23 } 24 ~Matrix() 25 { 26 if (pData) 27 { 28 delete[] pData; 29 pData = NULL; 30 } 31 } 32 int w; 33 int h; 34 int* pData; 35 protected: 36 private: 37 }; 38 39 void CreateMatrixList(list<Matrix>& myList) 40 { 41 Matrix a; 42 a.w = 3; 43 a.h = 3; 44 int size = a.w*a.h; 45 a.pData = new int[size]; 46 for (int i = 0; i < size; i++) 47 { 48 a.pData[i] = i; 49 } 50 myList.push_back(a); 51 } 52 53 int main() 54 { 55 list<Matrix> aList; 56 CreateMatrixList(aList); 57 58 auto pNode = aList.begin(); 59 int m = (*pNode).pData[2]; 60 61 return 0; 62 }
在第61行的位置设置断点,得如下结果:
得到m的值为2,程序能正常退出,运行成功!