本文主要使用详细的代码比较说明深拷贝和浅拷贝的区别,延伸讲到构造函数。并提供深拷贝和浅拷贝的对比代码。
目录
1 深拷贝和浅拷贝引入原因
2 深拷贝个浅拷贝基本介绍
3 浅拷贝的弊端
4 拷贝构造函数
5 类中有指针的浅拷贝
6 类中有指针的深拷贝
1 深拷贝和浅拷贝引入原因
拷贝简单理解就是赋值操作,就是将一个对象的数据拷贝给另一个同类型的数据。由于常规的拷贝直接使用赋值运算符实现,这是系统会默认生成一个默认的拷贝构造函数,这个拷贝构造函数是浅拷贝的。不会对对象中动态申请的空间中的内容进行拷贝,仅仅只是对申请空间首地址的拷贝,因此会引发安全问题,这也是浅拷贝和深拷贝引入的原因。
2 深拷贝个浅拷贝基本介绍
浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是在对象复制过程中的两种不同方式:
1 在未定义拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝(不用自己构造),它能够完成成员的简单的值的拷贝一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址(同一个堆区),当对象快结束时,会调用两次析构函数(析构函数也无需自己构造,但想要知道析构函数的工作可以自己构造析构函数用输出来记录),而导致指针悬挂现象,所以,此时,必须采用深拷贝。
2 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据(新的堆区空间进行拷贝),从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
3 浅拷贝的弊端
浅拷贝会使得空间进行重复释放,引起错误,这种错误一般会出现在类中有指针成员;
深拷贝需要单独构造拷贝构造函数,即对于指针成员需要在对象的实例化时,动态申请内存空间,而且不会引起内存的重复释放。
4 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建新对象时,会使用同一类中已存在的对象来初始化新创建的对象。拷贝构造函数在以下情况下会被调用:通过使用另一个同类型的对象来初始化新创建的对象;复制对象把它作为参数传递给函数;以及复制对象,并从函数返回。
对于基本数据类型,例如int,赋值操作如int a=4; int b=a;
就涉及到了拷贝。而对于复杂的类对象来说,由于其内部结构可能相当复杂,包含多种成员变量,因此拷贝过程就会相对复杂一些。
一个简单的例子:
#include<iostream>
using namespace std;
class Complex {
public:
double real, imag;
Complex(double r, double i) {
real = r;
imag = i;
}
};
int main(){
Complex cl(1, 2); // 创建一个复数对象cl,其实部为1,虚部为2
Complex c2 ( cl ); // 用复制构造函数初始化c2,使得c2的实部和虚部与cl相同
cout << c2.real <<","<< c2.imag << endl; // 输出结果应为"1,2"
return 0;
}
在这个例子中,我们定义了一个名为Complex
的类,它有两个成员变量:real
和imag
。然后我们创建了一个Complex
类型的对象cl
,并用它来初始化另一个对象c2
。在这个过程中,我们就用到了拷贝构造函数。
注意:对于浅拷贝来讲,系统默认的构造函数即可满足,且形如:Complex c2(cl); 的拷贝构造函数,与Complex c2 = cl;的结果是类似的,后者是运算符实现的。
5 类中有指针的浅拷贝
首先演示一段错误的代码:(这是一个有指针成员的类的实例)
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
namespace DAY11_SHALLOW_CPY
{
class Employee {
public:
char* name;
int age;
char* position;
string objName;
public:
Employee(const char* n, int a, const char* p,const string str) : name(new char[strlen(n) + 1]), age(a), position(new char[strlen(p) + 1]) ,objName(str){
strcpy(name, n);
strcpy(position, p);
cout << "构造函数 初始化参数" << endl;
}
// 浅拷贝构造函数
Employee(const Employee& other) : name(other.name), age(other.age), position(other.position) ,objName(other.objName) {cout << "构造函数 浅拷贝" << endl;}
~Employee() {
delete[] this->name;
delete[] this->position;
cout << this->objName << "析构函数" << endl;
}
void print() const
{
cout << "name: " << this->name << " age:" << this->age << " position: " << this->position << endl;
}
};
};
int main(int argc, char *argv[])
{
cout << "\n \n \n";
cout << "***************************************************浅拷贝解析***************************************************" << endl;
cout << "\n \n \n";
// 浅拷贝
{
using namespace DAY11_SHALLOW_CPY;
char hu1[] = "Tom";
char hu2[] = "ShangHai";
int ku1 = 23;
char hu11[] = "Hubery";
char hu22[] = "BeiJing";
int ku11 = 28;
cout << "*******step1*******" << endl;
Employee t1(hu1,ku1,hu2,"t1");
cout << "*******step2*******" << endl;
Employee t2 = t1;
cout << "*******step3*******" << endl;
strcpy(t2.name,hu11);
strcpy(t2.position,hu22);
t2.objName = "t2";
t2.age = ku11;
cout << "t1:********* " << endl;
t1.print();
cout << "t2:********* " << endl;
t2.print();
cout << "*******step4*******" << endl;
}
return 0;
}
运行结果:
根据结果可以看到,对实例t2中指针指向的空间的修改,改变了t1中相应的值,这就是浅拷贝,在拷贝时,仅仅只是拷贝了指针,而没有拷贝一份完整的数据出来,因此当一个实例修改数据时影响了另一个实例的数据,且在释放时,由于RALL机制,析构时释放,会出现重复释放。运行结果中“free(): double free detected in tcache 2” 就是这个意思。
解决这个问题的办法就是进行深拷贝。
6 类中有指针的深拷贝
解决5中重复释放的问题,代码如下:
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
namespace DAY11_DEEP_CPY
{
class Employee {
public:
char* name;
int age;
char* position;
string objName;
public:
Employee(const char* n, int a, const char* p,string objname){
this->name = new char[strlen(n) + 1];
this->position = new char[strlen(p) + 1];
this->age = a;
this->objName = objname;
strcpy(name, n);
strcpy(position, p);
cout << "构造函数 初始化参数" << endl;
}
// 深拷贝构造函数
Employee(const Employee& other) : age(other.age), name(new char[strlen(other.name) + 1]), position(new char[strlen(other.position) + 1]),objName(other.objName) {
strcpy(name, other.name);
strcpy(position, other.position);
cout << "构造函数 深拷贝" << endl;
}
~Employee() {
delete[] this->name;
delete[] this->position;
cout << this->objName << "析构函数" << endl;
}
void print() const
{
cout << "name: " << this->name << " age:" << this->age << " position: " << this->position << endl;
}
};
};
int main(int argc, char *argv[])
{
cout << "\n \n \n";
cout << "***************************************************深拷贝解析***************************************************" << endl;
cout << "\n \n \n";
// 深拷贝
{
using namespace DAY11_DEEP_CPY;
char hu1[] = "Tom";
char hu2[] = "ShangHai";
int ku1 = 23;
char hu11[] = "Hubery";
char hu22[] = "BeiJing";
int ku11 = 28;
cout << "*******step1*******" << endl;
Employee t1(hu1,ku1,hu2,"t1");
cout << "*******step2*******" << endl;
Employee t2 = t1;
cout << "*******step3*******" << endl;
strcpy(t2.name,hu11);
strcpy(t2.position,hu22);
t2.objName = "t2";
t2.age = ku11;
cout << "t1:********* " << endl;
t1.print();
cout << "t2:********* " << endl;
t2.print();
cout << "*******step4*******" << endl;
}
return 0;
}
运行结果:
根据结果可以看出,对t2实例中指针指向空间内容的修改并没有改变实例t1中的任何值。由此可见此段代码实现了深拷贝。
总的来讲,C++中默认的拷贝是浅拷贝,就是仅仅就是类中成员变量的直接复制,这点对于赋值运算符也是一样的。要想实现深拷贝,需要自己构建深拷贝构造函数。