关于C++复制构造函数

复制构造函数(copy constructor):单个对本类类型引用形参(常用const修饰)。当定义一个新对象并用同一个类型的对象对它进行初始化式,将显式使用复制构造函数,当该类型的对象传递给函数或从函数返回该类类型的对象时,将隐式调用复制构造函数。

复制构造函数的形参可以不为const,但必须是一个引用。否则每当以pass-by-value传递参数时,调用一个复制构造函数都会导致无穷递归。

如果不定义复制构造函数,编译器将合成一个,因此若要完全禁止复制(iostream类就不允许复制),禁止用户复制该类类型而且连友元和成员函数都不能进行复制:在private中声明但不定义一个复制构造函数。用户代码中的复制尝试将在编译时标记为错误(private),而成员函数和友元中的复制尝试将在连接时导致错误(未定义)

 

 1 #include <iostream>
 2 #include <vector>
 3 #include "Exmpl.h"
 4 
 5 void fun1(Exmpl obj){}
 6 
 7 Exmpl fun2(){
 8  Exmpl obj;
 9  return obj;
10 }
11 
12 int main()
13 {
14  Exmpl eobj;
15  fun1(eobj);
16 /* 调用复制构造函数,将形参Exmpl对象创建为实参Exmpl对象的副本,函数执行完毕后,调用析构函数撤销形参Exmpl对象 */
17  
18  eobj = fun2();
19 /* 
20    调用默认构造函数创建局部Exmpl对象,
21    函数返回时调用复制构造函数创建作为返回值副本的Exmpl对象(非引用类型作为返回值,将返回return语句中的值的副本),
22    然后调用析构函数撤销局部Exmpl对象,
23    调用赋值操作符,
24    最后调用析构函数撤销作为返回值副本的Exmpl对象 
25 */
26 
27   Exmpl *p = new Exmpl;
28 /* 调用默认构造函数动态创建Exmpl对象 */
29 
30   std::vector<Exmpl> evec(3);
31 /* 调用默认构造函数创建一个临时Exmpl,3次调用复制构造函数,将临时Exmpl复制到容器的每个元素,最后调用析构函数撤销临时Exmpl */
32 
33   delete p; // 析构函数撤销动态创建的Exmpl
34 
35   return 0;
36 /* 析构函数撤销eobj,vector的析构函数撤销evec,并调用Exmpl析构函数3次撤销3个Exmpl元素 */
37 }

 

     有些类必须对复制对象时发生的事情加以控制,这样的类经常有一个数据成员是指针或者有成员表示在构造函数中分配的其他资源

     把参数传递给函数有三种方法,一种是值传递,一种是传地址,还有一种是传引用。前者与后两者不同的地方在于:当使用值传递的时候,会在函数里面生成传递参数的一个副本,这个副本的内容是按位从原始参数那里复制过来的,两者的内容是相同的。当原始参数是一个类的对象时,它也会产生一个对象的副本,不过在这里要注意。一般对象产生时都会触发构造函数的执行,但是在产生对象的副本时却不会这样,这时执行的是对象的复制构造函数。为什么会这样?嗯,一般的构造函数都是会完成一些数据成员初始化的工作,在对象传递给某一函数之前,对象的一些属性可能已经被改变了,如果在产生对象副本的时候再执行对象的构造函数,那么这个对象的属性又再恢复到原始状态,这并不是我们想要的。所以在产生对象副本的时候,构造函数不会被执行,被执行的是一个默认的构造函数。当函数执行完毕要返回的时候,对象副本会执行析构函数,如果你的析构函数是空的话,就不会发生什么问题,但一般的析构函数都是要完成一些清理工作,如释放指针所指向的内存空间。这时候问题就可能要出现了。假如你在构造函数里面为一个指针变量分配了内存,在析构函数里面释放分配给这个指针所指向的内存空间,那么在把对象传递给函数至函数结束返回这一过程会发生什么事情呢?首先有一个对象的副本产生了,这个副本也有一个指针,它和原始对象的指针是指向同块内存空间的。函数返回时,对象的析构函数被执行了,即释放了对象副本里面指针所指向的内存空间,但是这个内存空间对原始对象还是有用的啊,就程序本身而言,这是一个严重的错误。然而错误还没结束,当原始对象也被销毁的时候,析构函数再次执行,对同一块系统动态分配的内存空间释放两次是一个未知的操作,将会产生严重的错误。

     上面说的就是我们会遇到的问题。解决问题的方法是什么呢?首先我们想到的是不要以传值的方式来传递参数,我们可以用传地址或传引用。没错,这样的确可以避免上面的情况,而且在允许的情况下,传地址或传引用是最好的方法,但这并不适合所有的情况,有时我们不希望在函数里面的一些操作会影响到函数外部的变量。那要怎么办呢?可以利用复制构造函数来解决这一问题。复制构造函数就是在产生对象副本的时候执行的,我们可以定义自己的复制构造函数。在复制构造函数里面我们申请一个新的内存空间来保存构造函数里面的那个指针所指向的内容。这样在执行对象副本的析构函数时,释放的就是复制构造函数里面所申请的那个内存空间。
 
     如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分,否则效果是运行基类的默认构造函数初始化对象的基类部分。
 
 
     补充:
     编译器总是显式(自动)调用派生类对象基类部分的析构函数。对象撤销的顺序和构造的顺序相反,首先运行派生类的析构函数,然后按继承层次依次向上调用各基类的析构函数
    虚析构函数:删除动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。处理继承层次中的对象时,指针的静态类型可能与被删除指针的动态类型不同,即可能删除实际指向派生类对象的基类类型指针。要保证运行适当的析构函数,基类中的析构函数必须为虚函数
 
 
 

posted on 2012-05-08 12:59  yfan.qiu  阅读(1323)  评论(0编辑  收藏  举报

导航