浅拷贝只是拷贝一个指针,并没有新开辟一个地址,拷贝的指针和原来的指针指向同一块地址,如果原来的指针所指向的资源释放了,那么再释放浅拷贝的指针的资源就会出现错误。
对于含有堆内存的类,如果使用默认构造函数浅拷贝操作,很容易会导致堆内存的重复删除,比如下面的代码:
#includeusing namespace std; class A { public: A() :m_ptr(new int(0)) { cout << "constructor A" << endl; } ~A(){ cout << "destructor A, m_ptr:" << m_ptr << endl; delete m_ptr; m_ptr = nullptr; } private: int* m_ptr; }; // 为了避免返回值优化,此函数故意这样写 A Get(bool flag) { A a; A b; cout << "ready return" << endl; if (flag) return a; else return b; } int main() { { A a = Get(false); // 运行报错 } cout << "main finish" << endl; return 0; }
运行结果:
可以看到“free(): double free detected in tcache 2”表示对同一块堆上的内存重复调用了free()函数。可以看到其中两个m_ptr的地址是相同的。
上述程序可以使用深拷贝来避免这个问题。
深拷贝不仅拷贝值,还开辟出一块新的空间用来存放新的值,即使原先的对象被析构掉,释放内存了也不会影响到深拷贝得到的值。在自己实现拷贝赋值的时候,如果有指针变量的话是需要自己实现深拷贝的。
上面浅拷贝的代码中,默认构造函数是浅拷贝,main函数的 a 和Get函数的 b 会指向同一个指针 m_ptr,在析构的时候会导致重复删除该指针。正确的做法是提供深拷贝的拷贝构造函数,比如下面的代码:
#includeusing namespace std; class A { public: A() :m_ptr(new int(0)) { cout << "constructor A" << endl; } A(const A& a) :m_ptr(new int(*a.m_ptr)) { //深拷贝一份相同的值 cout << "copy constructor A" << endl; } ~A(){ cout << "destructor A, m_ptr:" << m_ptr << endl; delete m_ptr; m_ptr = nullptr; } private: int* m_ptr; }; // 为了避免返回值优化,此函数故意这样写 A Get(bool flag) { A a; A b; cout << "ready return" << endl; if (flag) return a; else return b; } int main() { { A a = Get(false); // 正确运行 } cout << "main finish" << endl; return 0; }
运行结果:
运行结果正常。可以看到三个m_ptr的地址均不相同。
浅拷贝在对象的拷贝创建时存在风险,即被拷贝的对象析构释放资源之后,拷贝对象析构时会再次释放一个已经释放的资源,深拷贝的结果是两个对象之间没有任何关系,各自成员地址不同。
文章代码参考与<零声教育>的C/C++linux服务期高级架构系统教程学习



