• 读书笔记深度探索C++对象模型Chapter2


    Chapter 2 构造函数语义学

    关于C++,最常听到的一个抱怨就是:编译器背着程序员干了太多的事情。

    默认构造函数 default constructor

    如果没有程序员定义的构造函数,则会有一个default constructor被隐式(implicity)声明出来。一个被隐式声明的default ctor有时候会是一个trivialctor(完全什么也不做,连成员变量也不初始化,等于没有这个ctor,只是概念上有而已)。在以下四种情况下,这个隐式声明的default ctor会是nontrivial ctor

     

    情况1这个类含有带有default ctor的成员。

    例如:

    classFoo {public: Foo(),Foo(int);...};

    classBar {public: Foofoo; charstr;};

    void foo_bar(){

       Bar bar; //Bar::foo应在此处被初始化

       if(str){...}

    }

    被合成的Bar default ctor内含必要的代码,能够调用class Foodefault ctor来处理member object Bar::foo,但并不处理Bar::str。即被合成的default ctor只是为了满足编译器的需要(编译器需要有个地方来初始化Bar::Foo,因为它有自己的default ctor),而不是程序的需要(初始化Bar::str是程序员的动作)。

    如果程序员在ctor只显式初始化了Bar::str,则一些代码会被插入到这个ctor中。例如

    Bar::Bar(){ str=0;      }

    会被编译器扩展为

    Bar::Bar(){ foo.Foo::Foo(); //附加上的编译器代码

                str=0;//显式的程序员代码

    }

    情况2这个类派生自一个带有default ctorBase Class

    同样的道理,这个类既然派生自一个带有default ctorBase Class,则它需要一个地方来调用Base Classdefault ctor

    情况3这个类带有Virtual Function

    这种情况包括两个更细的情况:

    1.     这个类自己声明(或者继承)了Virtual Function

    2.     这个类继承自一个继承串链,其中有virtual base class

    这种情况下,编译时,要做如下工作:

    1.编译器需要生成一个virtual function table(vtbl)并填充。

    2.class object中,一个额外的pointer member(就是vptr,指向vtbl)会被编译器合成出来。此外虚拟调用会被替换(w.vf() => w.vprt[1])。

    为了支持这种功能,编译器必须为每个w对象设置它的vptr(这是成员变量,此时需要指向合适的vtbl),因此编译器需要在default ctor中安插一些代码来完成这种工作。

    情况4这个类带有Virtual Base Class

    考虑这样的代码:

    classX { public: inti; };

    classA : publicvirtualX   { public: intj; };

    classB : publicvirtualX   { public: doubled; };

    classC : publicA, publicB { public: intk; };

    //无法在编译期间解析出 pa->i 的位置(给一个pa无法确定i的地址)。

    void foo( constA* pa ) { pa->i = 1024; }

    main() {

       foo( new A );

       foo( new C );

       // ...

    }

     

    由于pa的真正类型不确定,所以某些编译器会记录一个指针例(如 __vbcX)来记录X,然后通过这个指针来定位pa指向的i。上述

    void foo( constA* pa ) { pa->i = 1024; }

    变成了:

    void foo( constA* pa ) { pa-> __vbcX ->i = 1024; }

    因此,__vbcX这个指针需要在object构造期间设置好。于是编译器需要一个default ctor来完成这个工作。

    复制构造函数Copy ctor的构造

    何时用到copy ctor:显式用t1初始化t2;传参;返回一个类对象。

    如果程序员显式定义了copy ctor,则调用它。

    如果没有,其内部是通过 default memberwise initialization的手法完成的(将源对象的所有member复制给目的对象,对于member class object,会递归执行memberwise initialization)。

    这些操作是如何构造的:

    概念上讲,这些操作是被一个copy ctor实现的。

    上述强调“概念上讲”,是因为有时候copy ctortrivial的。

    copy ctor何时是nontrivial:简单的答案为当class没有展现bitwise copy semantics时,copy ctornontrivial的。

    那么什么时候class没有展现bitwise copy semantics:答案为有四种情况。

    情况1这个类的某个member objectcopy ctor。(编译器要在这个类的copy ctor来调用其member objectcopy ctor)。

     

    情况2这个类继承自某个有copy ctorbase class。(编译器要在这个类的copy ctor来调用其base classcopy ctor)。

     

    情况3这个类声明了若干个virtual function

    如下代码

    void draw(const ZooAnimal& zoey) {zoey.draw();}

    void foo() {

       ZooAnimal franny = yogi;

       draw (yogi);   //调用 Bear::draw()

       draw (franny); //调用 ZooAnimal::draw()

    }

    如果ZooAnimal按照bitwise copy进行复制(ZooAnimal franny = yogi;),则会出现frannyvptr设置成了yogivptr,于是draw (franny);调用的会是Beardraw。(事实上不是,因为franny是一个实例,不是指针也不是引用)。

    因此ZooAnimal的复制构造函数需要显式设定vptr(使之指向ZooAnimalvtbl),这个设置动作需要在合成的copy ctor中完成。

    情况4这个类派生自的继承链中有virtual base class

    同构造函数的情况4 __vbcXXX需要显式重设,这个设置动作需要在合成的copy ctor中完成。

     

    RaccoonRedPanda中含有指向virtual base class subobject的指针(设为__vbcZooAnml),则当用RedPanda初始化Raccoon( Raccoon rc=rp;),将Raccoon-> __vbcZooAnml 设置为RedPanda->__vbcZooAnml是不对的。因此需要重新设置__vbcZooAnml,这个动作需要在copy ctor中完成。

    总结以上4种情况,bitwise copy semantics的意思可以理解为:类的某些成员变量(包括程序员定义的成员变量编译器所需要的变量如vptr__vbc等)不能按位复制时,需要调用成员变量的copy ctor或者重设vptr等编译器所需类的成员变量,这些动作都需要在发生对象复制的时候完成,因此编译器会合成一个copy ctor(入股没有的话)。

    程序转化语义学 Program Transformation Semantics

    显式的初始化操作 Explicit Initialization

    X x0;

    void foo_bar(){

       X x1(x0);   //定义了x1

       X x2 = x0;      //定义了x2

       X x3 = X(x0);   //定义了x3

    }

    转化的两个动作:重写每一个定义,其初始化部分被剥除;用copy ctor初始化。

    即变成了

    void foo_bar(){

       X x1;    //定义被重写,初始化操作被剥除

       X x2;    //定义被重写,初始化操作被剥除

       X x3;    //定义被重写,初始化操作被剥除

       //编译器安插X copy ctor

       x1.X::X( x0 );

       x2.X::X( x0 );

       x3.X::X( x0 );

    }

    其中x1.X::X( x0 );会表现为对copy ctor(即 X::X( constX& xx);)的调用。

    参数初始化 Argument Initialization

    如下代码的变化

    void foo(X x0);

    ...

    X xx;

    foo(xx)

    变成了

    void foo(X& x0);

    ...

    X __tmp;

    __tmp.X::X( XX );

    foo(__tmp);

    其中X声明了destructor,它在foo调用完成后销毁__tmp

    另一种变化是拷贝构建(copy construct),将实际参数直接建在其应该在的位置上。

    返回值的初始化 Return Value Initialization

    X bar(){

       X xx;

       ...

       return xx;

    }

    变成了

    void bar(X& _result){

       X xx;

       ...

       _result.X::X(xx);

       return;

    }

    对函数的调用

    X xx=bar();

    变为:

    X xx;

    bar(xx);

    对函数的调用

    bar().memfunc();

    变为:

    X _tmp;

    (bar(_tmp),_tmp).memfunc();

    在使用者层面做优化 Optimization at the User Level

    在编译器层面做优化 Optimization at the Compiler Level

    针对这种转化:

    X bar(){

       X xx;

       ...

       return xx;

    }

    变成

    void bar(X& _result){

       X xx;

       ...

       _result.X::X(xx);

       return;

    }

    这一转换的一个优化为:转变成如下代码

    void bar(X& _result){

       _result.X::X(xx);

       //原来处理xx,现在变为处理_result

       return;

    }

    这一优化称之为NRVNamed Return Value)。

    虽如此,NRV饱受批评。主因有二:编译器实现程度不一致(有些编译器)。函数变得复杂时,优化难以实施。

    成员初始化列表 Member Initialization List

  • 相关阅读:
    css做中划线与文字排版
    修复ios上第三方输入法弹出时输入键盘盖住网页没有进行相应滚动从而盖住表单输入框的问题
    一般活动页面之类简单的背景图内容布局方式
    compass的使用
    nodejs与sqlite
    ftp命令
    shell变量详解
    Vue CLI 3 使用百度地图
    centos7中安装python3
    redis集群安装问题/usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- redis (LoadError)
  • 原文地址:https://www.cnblogs.com/apprentice89/p/2973517.html
Copyright © 2020-2023  润新知