c++默认的拷贝构造函数是浅拷贝
浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如:
class A { public: A(int _data) : data(_data){} A(){} private: int data; }; int main() {
A a(5); A b = a; // 仅仅是数据成员之间的赋值 }
这一句b = a;就是浅拷贝,执行完这句后b.data = 5;
如果对象中没有其他的资源(如:堆,文件,系统资源等),则深拷贝和浅拷贝没有什么区别,但当对象中有这些资源时,例子:
class A { public: A(int _size) : size(_size) { data = new int[size]; } // 假如其中有一段动态分配的内存 A(){}; ~A(){delete [] data;} // 析构时释放资源 private: int* data; int size; } int main() { A a(5); A b = a; // 注意这一句 }
这里的b = a会造成未定义行为,因为类A中的复制构造函数是编译器生成的,所以b = a执行的是一个浅拷贝过程。我说过浅拷贝是对象数据之间的简单赋值,比如:
b.size = a.size; b.data = a.data; // Oops!
这里b的指针data和a的指针指向了堆上的同一块内存,a和b析构时,b先把其data指向的动态分配的内存释放了一次,而后a析构时又将这块已经被释放过的内存再释放一次。
对同一块动态内存执行2次以上释放的结果是未定义的,所以这将导致内存泄露或程序崩溃。
所以这里就需要深拷贝来解决这个问题,深拷贝指的就是当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象的另开辟一块新的资源,而不再对拷贝对象中有对其他资源的引用的指针或引用进行单纯的赋值。如:
class A { public: A(int _size) : size(_size) { data = new int[size]; } // 假如其中有一段动态分配的内存 A(){}; A(const A& _A) : size(_A.size) { data = new int[size]; } // 深拷贝 ~A() { delete [] data; } // 析构时释放资源 private: int* data; nt size; } int main() { A a(5); A b = a; // 这次就没问题了 }
总结:
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。
//说说以下代码的问题 #include <iostream> #include <cstdlib> #include <vector> using namespace std; class CDemo { public: CDemo():str(NULL){}; ~CDemo() { if(str) delete[] str; }; char* str; }; int main(int argc, char** argv) { CDemo d1; d1.str=new char[32]; strcpy(d1.str, "trend micro"); vector<CDemo> *a1=new vector<CDemo>(); a1->push_back(d1); delete a1; return EXIT_SUCCESS; }
这里最核心的问题归根结底就是浅拷贝和深拷贝的问题。如果CDemo类添加一个这样的拷贝构造函数就可以解决问题:
CDemo(const CDemo &cd) { this->str = new char[strlen(cd.str)+1]; strcpy(str,cd.str); };
这就是深拷贝。
总结一下
1. vector <CDemo> *a1 = new vector <CDemo>(); a1是new出来的,所以必须要手工delete.这是对a1本身而言,而与a1内存储的数据无关。
2. a1 -> push_back(d1); 这部操作比较复杂,因为你的vector是存储类,而不是类指针。所以首先会在栈上创建d1的一个拷贝d1_1,压入栈,作为参数传递给push_back。然后在push_back中,创建d1_1的拷贝d1_2,d1_2是存储在a1管理的内存中。然后push_back return,d1_1出栈,调用d1_1的析构。
3. delete a1; a1中存有d1_2,所以会删除d1_2,自然会调用d1_2的析构函数。
4. 在main中return 0, d1被自动删除,此时调用d1的析构函数。
5. 因为class CDemo没有拷贝构造函数,所以创建拷贝时只是简单的把新对象中每个成员变量的值设置成与原来的对象相等。相当于运行memcpy。这时问题就来了,因为你的一个成员是char *str; 这样d1,d1_1,d1_2的str都是指向同一个地址。所以只有第一次调用CDemo的析构函数时能运行正确,以后的都会出错。因为一个地址只能释放一次。
6. 如果你的vector改为vector <CDemo*> *a1 = new vector <CDemo*>(); 即存储类指针,那么在执行delete a1之前,还要手工去删除vector中的每个元素。
7. 如何验证:在析构函数中用cout输出字符串。