拷贝构造函数

时间:2021-07-18 13:57:15

           在介绍拷贝构造函数之前,先来说几个背景知识。首先来说一下c++里的referrence。referrence可以看作是一个常量指针,在定义一个refferrence的时候必须为他指派一个地址,而且以后不能再更改。

int x = 0;
int &y = x;

b.y = 1;
y++;//对y的更改就是对x的更改。。执行完之后y和x的值都是1

          大家看到的比较多的应用referrence的地方就是作为函数的参数或者返回值。在函数内对referrence参数的更改都会改变函数外参数的值。

          下面在来看看c++中函数参数是如何进行传递的,c++在调用函数之前都会将参数由右至左进行压栈(类的成员函数会多压一个this指针),再将当前地址进行压栈,以记录下函数执行完之后返回的地址,然后再跳转到函数代码首地址进行执行。进入函数之后,再将函数的local variable进行压栈。所以在一个函数真正开始执行它的代码的时候,堆栈的情况应该是这个样子的:

拷贝构造函数

        好了,参数,return address,local variables都是保存在栈上,那么函数的返回值放在哪了呢?我们来看看一个函数在编译之后,它的汇编输出是个什么样子。

int f(int x, char c);
int g = f(a, b);

push b
push a
call f()
add sp,4
mov g, register a//返回值保存在了寄存器中。

如果返回char, int, float, double这些c++数据类型是没问题的,但是如果返回一个struct,而这个struct又非常的大呢?寄存器不够放,怎么办?c++编译器是这么做的,将存放返回值的地址作为一个参数进行压栈,函数执行完再将返回的内容copy到存放返回值的地方那。这种copy的方式对于c的struct当然没问题,但是对于c++的对象,这种处理就不妥了。来看看这段代码

#include <fstream>
#include <string>
using namespace std;
ofstream out("HowMany.out");
class HowMany {
        static int objectCount;
        public:
                HowMany() { objectCount++; }
                static void print(const string& msg = "") {
                        if(msg.size() != 0) out << msg << ": ";
                                out << "objectCount = "<< objectCount << endl;
                }
                ~HowMany() {
                        objectCount--;
                        print("~HowMany()");
                }
};
int HowMany::objectCount = 0;
// Pass and return BY VALUE:
HowMany f(HowMany x) {
        x.print("x argument inside f()");
        return x;
}
int main() {
       HowMany h;
       HowMany::print("after construction of h");
       HowMany h2 = f(h);
       HowMany::print("after call to f()");
} ///:~

这段代码执行的结果如下:

after construction of h: objectCount = 1
x argument inside f(): objectCount = 1
~HowMany(): objectCount = 0
after call to f(): objectCount = 0
~HowMany(): objectCount = -1
~HowMany(): objectCount = -2

这当然不是我们。为什么最后的答案是-2。这是因为在进入函数f的时候,对象进行了两次copy,一次copy是函数的参数copy了一次,第2次是返回h2时又进行了一次copy。而这两次copy,都是直接将对象复制,并没有调用构造函数,所以objectCount就没有进行自增,而是简单的赋值。所以在这两个copy出来的对象出了作用域时就多调用了两次析构函数,结果就变成了-2。

         那么如何解决这种问题的发生呢?c++就引入了copy-constructor(复制构造函数)。CC的用处就是从一个已经存在的对象创建一个新的对象,而不是简单的复制-这也就是深拷贝的意思,反之简单复制就是潜拷贝(默认CC都是潜拷贝)。再以下三种情况会调用CC:

  1. ClassA  a = b;//类似这种对象定义,b为ClassA类型
  2. 对象作为函数的形参进行传递,但是没有用引用或地址的时候
  3. 对象作为函数的返回值返回,没有用引用或传址的时候

CC的一般形式:X(const X& x){...;}

       利用拷贝构造函数实现深拷贝一般都是为了将类里面的像指针这样的成员指向的数据重新复制一个,而不是简单的将指针的里的地址进行复制。这样在复制对象的时候不会因为析构了一个对象,就把其他复制出的对象里指针指向的内容释放而导致错误。但是用了深拷贝也带来了一定的麻烦,如果指针指向的是一个很大的数据,这样在进行对象复制的时候,会很耗费时间。所以c++中就引入了智能指针,其实就是一个含有引用计数的指针,当引用计数为0时才会释放指向的内容。智能指针留着下一篇文章来说说吧

 

引用资料:

[1]Bruce Eckel,<<Thinking in C++>>