写在前面

今天咱们主要来探究一下,我们为啥需要拷贝构造这个东西,以及拷贝构造的一些基本语法。

正文

在这里呢,我先给一段测试程序,探究一下没有拷贝构造对于类到底会有什么影响,这段程序我也是参考了浙江大学的翁恺老师教授C++课程时上课的例程,感谢翁恺老师的无私奉献!

这段程序看起来比较复杂,让我来慢慢分析一下。首先我定义了一个static的变量i,为什么要是static的呢?因为我需要i是全文件可见的。

然后我构造了一个A类,在A类的构造函数里面,我将全局的i进行自加1,并且输出一些相关信息,在A类的析构函数里面,我将全局的i进行自减1,并且输出一些相关信息。为什么要这么做呢,其实就是为了记录A类生成对象的时候,构造函数和析构函数被调用的情况。

那么在A类里面还有print_i()这个函数,也就是为了打印调用函数时刻的i值。

那么在这个测试程序里面发生了几次对象的实例化呢?

仔细数数一共是3次,a, a2f()函数的形参a_t,当调用f()函数的时候,f()函数会根据实参a来构造出一个形参a_t来。

那么我们来看看程序的结果输出是什么:

正常来说,当最后程序结束的时候,i的数值应该是0,但是结果确实-2,说明此时程序肯定是有了问题的。

为了说明这个问题,我需要一些先备知识,那就是关于c++的=号的,在c++里面将对象a赋给b,也就是做对象的a=b的时候,这个=时如何起作用呢?那就需要知道c++里面的运算符重载相关知识了,c++里面的运算符是可以重载成用户需要的功能的,也就是说=实现的功能可以是将两边的变量或者常量进行相减(当然,我非常不建议这样做)。好了,言归正传,在这个测试程序里,我们并没有重载=号,所以如果用了=号的时候,程序应用的就是默认的=功能。

那么如何进行对象的默认赋值呢?也就是说对象的a=b到底发生了什么呢?其实这个发生的就是拷贝工作,不过这个拷贝不是按字节进行拷贝的,而是按照成员进行拷贝的,不是bitwise的拷贝,而是memberwise的拷贝。所以对象的a=b的时候,是将对象b里面放的成员变量按照成员拷贝到对象a里面的。

好了,回到了我们的测试程序,首先我们构造出了a这个对象,此时i=1,这没有问题,接下来我调用了f(),此时就发生了一次对象的拷贝,因为将实参对象a拷贝成了形参a_t,发生了memberwise的拷贝,但是此时i没有进行自加,这说明构造函数没有被调用,这就很奇怪了,导致函数结束的时候调用了对象的析构函数,i进行了自减,导致i变成了0。

接下来A a2=f(a);又发生了一次memberwise的拷贝,此时构造函数又没有被调用,因为此时的i是0。最后,aa2都被析构了,导致i进行了2次自减操作从而变成了-2。

经过我们分析,问题就很明白了,当对象发生memberwise的拷贝的时候并不会调用默认构造函数,也就是default constructor,这是很危险的,一个对象做出来的时候没有被初始化过是非常危险的,因此我们要解决它,解决方式就是拷贝构造函数。拷贝构造函数的作用就是用已有的对象来初始化一个新的同类对象,也就是说做对象的a=b的时候,拷贝构造函数是会被调用的,可以理解成将对象b做为参数来实例化类A得到对象a,看下面这段例程感受一下:

结果如下:

可以看到i最后变成了0,说明拷贝构造函数起作用了。

所以,为了保证程序的正常运行,以后写类的时候,记得要写一个拷贝构造函数。