拷贝构造函数之浅复制与深复制
拷贝构造函数分为默认拷贝构造函数和自定义拷贝构造函数,默认拷贝构造函数是编译器自动为类对象创建的构造函数,而自定义拷贝构造函数则是根据自己需要创建的函数
特性
默认拷贝构造函数:简单地将参数对象的每个数据域复制给新建对象中的相应的副本。这个过程使用的是逐位复制(相当于赋值)的方式(最低成本的方式),是一种浅复制
自定义拷贝构造函数:通过在函数里创建新的对象来存放复制过来的数据,从而实现深复制
注意
当类定义里面有带有形参的一般构造函数或自定义的拷贝构造函数时,如果不显式地声明一个没有形参的默认构造函数,此时编译器不会自动创建没有形参默认构造函数,所以定义一个没有实参的对象是不允许的。
//编译器自动创建构造函数
#include<iostream>
cladss foo() {
private:
int m_a;
}
int main() {
foo b; //此时编译器会自动创建默认构造函数,可以这样定义一个对象
}
//编译器不自动创建默认构造函数
class foo(){
public:
foo(int a) {
m_a = a;
}
foo(foo & b){
m_a = b.m_a;
}
private:
int m_a;
}
int main() {
foo c; //error,此时编译器没有自动创建无形参的默认构造函数,所以没有函数能够定义这个对象
foo d(1); //ok
foo e(d); //ok
}
浅复制与深复制
当类里面没有指针对象时,使用c++自动创建的拷贝构造函数是没有问题的。但是,如果类里面有指针成员,使用浅复制就会出现错误,此时,我们应该自定义拷贝构造函数来实现深复制。
#include<iostream>
class student{
public:
student() {
id = 0;
name = new string("abc");//动态创建空间
}
student(student & stu) {
name = stu.name;
}
~student() {
delete name;//销毁对象前释放空间
name = NULL;//使指针指向空,避免出现悬垂指针
}
string getname() {
return *name;
}
private:
int id;
string* name;//定义了一个指向string类型的指针
};
/*该程序会导致运行时错误。因为类里面的是默认的拷贝构造函数,是浅复制,所以,直接把对象a中指向name的指针给了b对象中的name,这时候,两个name所指的是同一片内存区域。当类对象使用完后会调用析构函数,删除之前开辟的空间,因为两个name都指向同一片区域,所以该区域会被释放两次,所以会导致运行时错误。*/
int main() {
student a;
student b(a);
cout << a.getname() << endl;
cout << b.getname() << endl;
return 0;
}
//正确的做法是自己定义一个拷贝构造函数
#include<iostream>
#include<string>
using namespace std;
class student{
public:
student() {
id = 0;
name = new string("abc");
}
//自己定义的拷贝构造函数,为新的对象先开辟一块新的空间,再将旧对象的内容复制过去。
student(student & stu) {
name = new string();
*name = stu.getname();
}
~student() {
delete name;
}
string getname() {
return *name;
}
private:
int id;
string* name;
};
//程序可以正确运行
int main() {
student a;
student b(a);
cout << a.getname() << endl;
cout << b.getname() << endl;
return 0;
}
指向类的指针的深复制
#include<iostream>
using namespace std;
class Date {
public:
Date() {}
Date(int y, int m, int d):year(y), month(m), day(d) {}
int getYear() {return year;}
private:
int year, month, day;
}
class Person {
public:
Person (int id, int year, int month, int day);
Person (Person&);
~Person();
private:
int id;
Date* birthday;
}
//正确实现
Person::Person (int id, int year, int month, int day) {
this->id = id;
birthday = new Date(year, month, day); //在申请一块存放Date类型数据的同时用参数初始化这个空间里的数据
}
Person::Person (Person& person) {
this->id = person.id;
birthday = new Date(person.birthday->getYear(), 1, 1); //同上,要在申请的时候初始化
}
//错误实现
Person::Person (Person& person) {
this->id = person.id;
birthday = new Date;
birthday = person.birthday;
/*这样做虽然为birthday申请了一块存放Date类型数据的空间,但是,后面的一个语句等于让birthday指针指向了person的birthday指针指向的内存空间,这样反而造成了内存泄露。所以这样并没有实现深复制。*/
}
小结
当类的数据成员里有指针的时候,需要自己定义拷贝构造函数,实现深复制。
当类的数据成员没有指针的时候,只需要利用c++提供的默认拷贝构造函数即可。
把指针深复制给指针的时候一定要注意指针重名和内存泄露的问题。