拷贝构造函数

拷贝构造函数的形式是固定的:类名(const 类名 &)

  1. 该函数是一个构造函数 —— 拷贝构造也是构造!
  2. 该函数用一个已经存在的同类型的对象,来初始化新对象,即对对象本身进行复制

类会默认提供拷贝构造函数,但是它存在问题。

002拷贝构造函数.png

如果是默认的拷贝构造函数,pc2 会对 pc 的 _brand 进行浅拷贝,指向同一片内存;pc2 被销毁时,会调用析构函数,将这片堆空间进行回收;pc 再销毁时,析构函数中又会试图回收这片空间,出现 double free 问题。

所以,如果拷贝构造函数需要显式写出时(该类有指针成员申请堆空间),在自定义的拷贝构造函数中要换成深拷贝的方式,先申请空间,再复制内容。

003拷贝构造函数.png

拷贝构造函数的调用时机(重点)

1-当使用一个已经存在的对象初始化另一个同类型的新对象时

004拷贝构造函数.png

2-当函数参数(实参和形参的类型都是对象),形参与实参结合时(实参初始化形参)

1
void func(Student s);

说白了,上面这个就是值传递,值传递肯定是要拷贝的。

建议将函数参数用引用,即 void func(Student & s)。避免拷贝。

3-当函数的返回值是对象,执行return语句时

我们可以选择,将返回值定义为返回引用来避免拷贝。但同时你要注意,万不可返回一个临时对象的引用。

 

第三种情况直接编译并不会显示拷贝构造函数的调用,但是底层实际是调用了的,加上下面的优化参数进行编译可以看到效果。

1
g++ CopyComputer.cc -fno-elide-constructors

拷贝构造函数的形式探究

思考1:拷贝构造函数是否可以去掉引用符号?

如果拷贝函数的参数中去掉引用符号,进行拷贝时调用拷贝构造函数的过程中会发生“实参和形参都是对象,用实参初始化形参”(拷贝构造第二种调用时机),会再一次调用拷贝构造函数。形成递归调用,直到栈溢出,导致程序崩溃。

思考2:拷贝构造函数是否可以去掉 const?

加 const 的第一个用意:为了确保右操作数的数据成员不被改变。

加const的第二个用意:为了能够复制临时对象的内容,因为非const引用不能绑定临时变量(右值)

实现拷贝构造函数(重点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

class Point {
private:
int *coordinates; // 动态分配的数组
int dimension; // 维度

public:

// 拷贝构造函数
Point(const Point &other)
: dimension(other.dimension)
{
coordinates = new int[dimension]; // 动态分配数组
for (int i = 0; i < dimension; ++i) {
coordinates[i] = other.coordinates[i]; // 深拷贝
}
std::cout << "Copy constructor called" << std::endl;
}
};

拷贝构造函数本身也属于构造函数,因此可以利用初始化列表初始化,而对于实际的数据拷贝,可以在函数体完成。这种初始化方式已在构造函数章节讲过了。

实际上拷贝构造函数就是把此类的构造函数重新行为一遍,只不过接受的参数是一个同类对象。

构造函数就是初始化成员变量,指针成员变量还要申请堆空间来初始化,如果有实际数据,就在函数体中初始化。那么拷贝构造函数和构造函数唯一的不同是,拷贝构造函数是用存在的同类对象初始化正在创建的同类对象的成员变量,构造函数是用全部或部分的成员变量参数来初始化正在创建的同类对象的成员变量。