一、系统生成默认拷贝构造函数
作为六大默认成员函数之一,如果没有手动写默认拷贝构造函数,系统会自动生成一个默认拷贝构造函数,其作用是将对象中内置成员变量的值拷贝到新的对象中,其实际上完成的工作就是将对象空间中的所有内置类型成员的空间中的内容拷贝到新对象中。
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 2, int day = 6)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
};
int main()
{
Date d1(2022, 2, 1);
Date d2(d1);
d1.Print();
d2.Print();
return 0;
}
二、浅拷贝与深拷贝
既然系统默认生成的默认拷贝i构造函数已经能够完成值的拷贝,我们是否还有手动写拷贝构造函数的必要?答案显然是有必要。
我们看下面代码,实现栈的拷贝:
class Stack
{
private:
int size;
int capacity;
ElemType* data;
public:
Stack(int n = 4)
{
data = (ElemType*)malloc(sizeof(ElemType) * n);
size = 0;
capacity = n;
}
~Stack()
{
free(data);
data = nullptr;
size = 0;
capacity = 0;
}
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
我们想创建一个栈st1,再创建第二个栈st2,并且使用系统默认生成的拷贝构造函数,结果程序崩溃了,其原因是在拷贝时,只有st2将st1中指针的只也拷贝过去,在st2生命周期结束之后,将该空间已经释放了,当st1生命周期结束时,再一次去释放那块空间,但是此时那块空间已经不属于程序,所以st1无权释放该空间。
其最根本的原因就是st2进行拷贝的时候,只进行了值拷贝,没有考虑空间的问题,这种拷贝也叫做浅拷贝,或者值拷贝。为了解决这个问题,我们需要对开辟了空间的指针另作考虑,再为其开辟一块空间,这种拷贝叫做深拷贝。
于是乎,栈的拷贝函数应该如下:
Stack(const Stack& s)
{
ElemType* tmp = (ElemType*)malloc(sizeof(ElemType) * s.capacity);
if (tmp == nullptr)
{
perror("copy error");
exit(-1);
}
memcpy(tmp, s.data, sizeof(ElemType) * s.capacity);
data = tmp;
}
这样下来,我们为st2的data指针冲刺你申请了一块内存并将st1中data所指空间的内容拷贝到了st2中,程序就能够成功运行了。
三、对象的传值调用
结论1
若函数的参数为类的传值调用时,调用该函数之前会调用参数类的拷贝构造函数。
class Date { private: int _year; int _month; int _day; public: Date(int year = 2024, int month = 2, int day = 6) { _year = year; _month = month; _day = day; } Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } }; void fun1(Date d) { return; } void fun2(Date& d) { return; } int main() { Date d1(2022, 2, 1); fun1(d1); fun2(d1); }
我们逐步运行代码,观察其调用堆栈,我们发现,在调用fun1和fun2时,二者的调用堆栈如下:
从图中我们可以得知,当函数参数为类的传值调用时,系统会自动调用其类的拷贝构造函数。
结论2
传值调用对象时,会创建一个临时对象,并调用其构造函数利用参数对临时对象进行初始化。
class Date { private: int _year; int _month; int _day; public: Date(int year = 2024, int month = 2, int day = 6) { _year = year; _month = month; _day = day; } ~Date() { cout << this << endl; } }; void fun(Date d) { return; } int main() { Date d1(2022, 2, 1); fun(d1); return 0; }
通过以下代码,类的析构函数会打印this指针所指向地址。
我们发现,两次this指针的地址不一样,说明,fun函数中创建了一个临时对象,然后调用拷贝构造函数利用传过来的对象进行初始化。