zoukankan      html  css  js  c++  java
  • 深度探索C++对象模型之第二章:构造函数语意学之Copy constructor的构造操作


       C++ Standard将copy constructor分为trivial 和nontrivial两种:只有nontrivial的实例才会被合成于程序之中。决定一个copy constructor是否是nontrivial的,则是由classs是否具有 bitwise copy semantics,在以下四种情况下:class 不具有bitwise copy semantics,如果一个已经声明的类缺乏copy constructor ,编译器为了正确处理“以一个 class object 作为另一个class object的初值”,编译器会为class合成一个copy constructor。

    • 当class内含一个member object,而这个member object的class声明中有一个copy constructor(不论是用户explicitly define还是编译器合成的)
    • 当class继承自一个base class,而这个bass class 存在 copy constructor(无论是用户explicitly define  or 编译器合成的)
    • 当classs声明了一个或多个virtual function
    • 当类派生自一个继承串链,其中有一个或多个 virtual base class时

    对于以上四种情况,类不再具有bitwise copy semantics(逐位次拷贝语义学)时,而且默认构造函数若未被声明的话,default constructors会被视为nontrival.


    一、下面来介绍下什么叫bitwise copy semantics:

      给两个例子:大家自己对比一下吧

    比如下面这个例子就展示了 bitwise copy semantics:

    1 class Word{
    2 public:
    3     Word (const char*) ;
    4    ~Word() {delete [] str;} 
    5 private:
    6    int cnt;
    7    char *str;  
    8 };

     而下面这个例子Word类就不再展现出bitwise copy semantics,编译器必须合成出一个copy constructors,以便调用member class String object的copy constructor.

    class Word{
    public:
        Word(const String&);
       ~Word(); 
    private:
       int cnt;
       String str;   
    };
    
    //其中String声明了一个explicit copy construcotr
    class String{
    public:
       String(const char*); //显示声明的默认构造函数
       String(const char &);//explicit copy constructors
       ~String();  
        
    };

     则针对上述情况,编译器为类Word合成一个copy consructor,如下所示:注意正如我们期待的一样:在被合成的copy constructor中,如整数、指针、数组等nonclass members也会被复制。

    1 //C++伪代码
    2 inline Word::Word(const Word& wd)
    3 {
    4  str.String::String(wd.str);
    5  cnt = wd.cnt;   
    6 }

    二、下面介绍三种会以一个object的内容作为另一个class object初值的情况:

     第一种情况就是对一个object做显示的初始化操作:

    class X{...};
    X x;
    
    //显示的用一个object的内容作为另一个class object的初值
    X xx = x;

     第二种情况就是当object被当做某个参数传递给某个函数时;

    1 extern void foo(X x);
    2 void bar()
    3 {
    4  X xx;
    5 //以xx作为foo()第一个参数的初值(隐式的初始化操作)
    6 foo(xx);
    7 }

     第三种情况就是当函数传回一个class object时:

    1 X foo_bar()
    2 {
    3    X xx;
    4   return xx;
    5 }

     当class object以相同class的另一个object作为初值时,其内部是以所谓的default memberwise initialization(逐成员初始化)完成的:即把每一个内建或派生的data member的值,从一个object拷贝到另一个object上,不过他并不会拷贝其中的class object,而是递归的实行member initializaiton.


    三、下面我们讨论四种情况的第三种当classs声明了一个或多个virtual function

      当class声明了一个或多个virtual function时,编译器会发生以下扩张行为:

    • 增加一个virtual function table(vtbl),内含每个有作用的virtual function地址
    • class objet增加一个指向vtbl的指针(vptr).

    当编译器导入一个vptr(是新导入一个vptr,例如将base class object以derived class的object内容初始化时,重要!!!)时,class就不再展现bitwise copy semantics啦,所以编译器需要合成一个copy constructor,来让vptr正确的初始化:

    来看一个例子:

     1 class ZooAnimal{
     2 public:
     3    ZooAnimal();
     4    virtual ~ZooAnimal();
     5    virtual void animate();
     6    virtua void draw();
     7 
     8 private:
     9    //ZooAnimal的内建数据...
    10 };
    11 
    12 class Bear : public ZooAnimal{
    13   Bear();
    14   void animate(); //virtual function 
    15   void draw() ; //虚函数
    16   virtual void dance();
    17 private:
    18    //Bear的内建函数
    19 
    20 };

     当以一个ZooAnimal object作为另一个ZooAnimal object的初值 或 以一个Bear object作为另一个Bear object的初值时,此时并不是新导入一个vptr,virtual function table仍然是原有的表,所以此时具有bitewise semantics,此时编译器要产生的拷贝构造函数是trivial(无意义的),根本不会合成。

    举个例子如下:

    Bear yogi;
    Bear whii = yogi;

     经过上述操作之后yogi oobject和whii object中分别含有的vptr指向的vtbl是相同的,如下图所示:

     而如果一个base class object以一个derived class object的初值初始化时,则此时base class 就不再具有bitwise semantics

    举个例子:

    1 Bear yogi;
    2 ZooAnimal whii = yogi;

    此时编译器需要合成一个nontrivial的copy structor来显示设定base object的vptr指向base class的vptr,而不是直接从右边derived class object中将vptr的值拷贝出来。 如下图所示:


     四、下面我们讨论四种情况的第三种:处理virtual Base class实例

           bitwise copy semantics失效发生在一个class object以其derived object作为初值时,而不是发生在一个class object 以一个同类的object作为初值时,(后者使用bitwise copy拷贝绰绰有余)

     举个例子如下:

      

    //定义一个浣熊类
    class Raccoon : public virtual ZooAnimal {
    public:
       Raccoon() {   } 
       Raccoon(int val) {   }
    private:
       //Raccon的内建数据
    
    };
    
    //定义一个大熊猫类 继承自浣熊
    class RedPanda : public Raccoon{
    public:
        RedPanda() {   }
        RedPanda(int val) {  }
    private:
       //RedPand的内建数据
    };

     记住浣熊才是这个具有虚基类的类,需要编译器合成trivial的 copy constructor就发生在当 浣熊以大熊猫作为初值时。因为每个编译器对于虚拟继承的承诺,都代表必须让 “derived class object中的virtual base class subobject位置”在执行期就准备妥当,即如下情况:

    RedPanda little_red;
    Raccoon  little_critter = little_red;

    在这种情况下,编译器必须合成一个copy constructor,安插一些代码以设定virtual base class的初值(或只简单的确定它没有被抹除),对每一个members执行必要的memberwise初始化操纵,以及执行其他的内存相关操作。


     好啦,本节关于何时编译器会合成一个nontrivial的copy constructor的讨论就结束啦,大家要记得复习哦。

    陈小洁的三只猫
  • 相关阅读:
    正则表达式
    浏览器加载时间线
    浏览器事件
    脚本化CSS
    定时器元素大小位置属性等 20181231
    关于行内元素 20181229
    个人冲刺01
    周总结
    团队冲刺10
    团队冲刺09
  • 原文地址:https://www.cnblogs.com/ccpang/p/11367344.html
Copyright © 2011-2022 走看看