C/C++修炼之道01——复制构造函数(copy constructor)

定义:

      只有单个形参,该形参是对本类类型对象的引用(常用const修饰),这样的构造函数成为复制构造函数。

使用方式:

(1)  显示使用----用一个同类型的对象初始化该对象时;

(2)  隐式使用----将该类型的对象传递给函数或从函数返回该类型对象时。

三种类型的复制构造函数:

(1)  自定义的copy constructor:由类设计者定义——因为有些类必须对复制对象加以控制,如数据成员是指针的情况或者数据成员表示在构造函数中分配的其他资源,或类在创建新对象时必须要做一些特定工作,这时候就需要定义自己的复制构造函数;

        如果class中没有提供一个explicit copy constructor,其内部是以所谓的default memberwise initialization手法完成复制拷贝,也就是把每一个内建的或派生的data member(例如一个指针或一个数组)的值,从某个object拷贝到另一个object身上。不过它并不会拷贝其中的member class object,而是以递归方式施行memberwise initialization。分为如下两种情况:

(2)  bitwise copy constructor :逐位复制——默认方式;

(3)  合成的 copy constructor :编译器合成——在(2)失效的情况下。

浅拷贝与深拷贝:

        首先对上面的一个过程做一个澄清,如果一个类中没有显式的定义一个拷贝构造函数,在执行赋值操作时编译器并不一定会合成一个拷贝构造函数,这种情况子下,编译器通过default memberwise initialization手法完成复制拷贝,当在bitwise copy semantics有效时,编译器才会合成默认的构造函数。

        default memberwise initialization 是C模型内部的一种实现方案,其原理就是对同一类的两个对象直接的赋值进行了暗箱操作,说白了,就是将一个对象内存空间中的数据,原封不动的拷贝出另一份来填满另一个对象的内存。

        试想,如果类中有一个指针成员变量,而其指向堆中的一片区域,然而在赋值过程中,根据memberwise的概念,只是将指针的值进行了赋值,这样一来,这两个对象中的指针变量自然就指向同一片内存区域了,这就是所谓的浅拷贝,如下所示:

浅拷贝shllow copy

例子:

  1. <span style="font-size:24px;">class Base{  
  2. public:  
  3.          int a;  
  4.          char ch;  
  5.          char* str;  
  6. }  
  7. Base b1;  
  8. b1.a=15;  
  9. b1.ch='c';  
  10. b1.str="string";  
  11. b2=b2;</span>  

        现在看看b1和b2的内存布局情况:


        可以看到:b1.str和b2.str指向了同一内存空间,即当一方撤销时,另一方将受到影响,故应极力避免这种情况,这时就需程序员自己来实现拷贝构造函数来完成那片内存的拷贝赋值操作,即所谓的深度拷贝。


深度拷贝:deep copy——挨个成员拷贝

        再看b1和b2的内存布局:


此时b1.str和b2.str指向了不同的内存空间,但内容(string)还是一样的。


        在没有显式拷贝函数的情况下,编译器默认是bitwise copy semantics;在bitwise copy失效的情况下,编译器必须合成一个copy constructor,这两种情况均属于浅拷贝

须注意的是,以上编译器合成copy constructor和bitwise copy constructor两种情况,均不需要类设计的参与


        以下谈谈在什么 情况下bitwise copy semantics会失效?

四种情况

(1)  当class内含一个class member object ,而后者的class声明有一个copy constructor 时(被class设计者明确声明或被编译器合成)——递归调用;

(2)  当class继承自一个base class,而后者存在一个copy constructor时(被class设计者明确声明或被编译器合成)——递归调用;

(3)  当class声明了一个或多个virtual functions时——因为此时要考虑类对象中的虚函数表指针vptr的值。

        当在同层对象之间进行初始化时,bitwise copy已经够用了(为简化,此时不考虑含有指针成员的情况);

例子:

  1. <span style="font-size:18px;">class ZooAnimal{  
  2. public:  
  3.       ZooAnimal();  
  4.       virtual ~ZooAnimal();  
  5.         
  6.       virtual void animate();  
  7.        virtual void draw();  
  8. .....  
  9. };  
  10. class Bear : public ZooAnimal{  
  11. public:  
  12.       Bear();  
  13.       void animate();  
  14.       void draw();  
  15.       virtual void dance();  
  16. ......  
  17. };  
  18. Bear yogi;  
  19. Bear winnie = yogi;  //bitwise copy</span>  

        其内存布局如下:

        但是如果存在:当一个base class object以其derived class 的object内容做初始化操作时,此时若bitwise copy显然会出错,因为其vptr将指向不同的虚表,故此时编译器需要合成copy constrctor,以保证vptr复制操作的安全。合成出来的base class object copy constructor会明确是定object的vptr指向base class object 的vitrual table,而不是直接从右手边的class object 中将其vptr现值拷贝过来。

如:

  1. <span style="font-size:18px;">void draw(const ZooAnimal& zoey )  
  2. {  
  3.      zoey.draw();  
  4. }  
  5. ZooAnimal franny = yogi;//发生切割(sliced)行为  
  6. draw(yogi); //调用Bear::draw();  
  7. draw(franny); //调用ZooAnimal::draw()</span><span style="font-size:16px;">  
  8. </span>  
        其内存布局如下(此时由编译器合成copy constructor):


(4)  当class派生自一个继承串链时,其中有一个或多个virtual base classes时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值