• C++学习之Function语意学(第四章)


    读起C++对象模型,有点吃力。真的很吃力。。

    P140
    C++支持三种类型的成员函数:static,nonstatic,virtual.
     
    P141
    C++的设计准则之一就是:nonstaic member function至少必须和一般的nonmember function有相同的效率。在设计上为了支持这一准则,编译器内部已将"member 函数实体"转换为对等的"nonmember 函数实体"。下面是转化的步骤
     
    1. 改写函数的signature(意指函数原型)以安插一个额外的参数到member function中,用以提供一个存取管道,使class object得以调用该函数。该额外参数被称为this指针。
    Ex1:
    //non-const nonstatic member function
    Point3d Point3d::magnitude()
    >> Point3d Point3d::magnitued(Point3d *const this)
    //其中的const意思是this指针不能指向其他的地址。
     
    Ex2: //const nonstatic member function
    Point3d Point3d::magnitude()const
    >> Point3d Point3d::magnitude(const Point3d *const this)
    //其中第一个const的意思是 this 指针指向的Point3d对象的内容不能变更
    //其中第二个const的意思是 this 指针不能指向其他的地址
     
    2. 将每一个"对 nonstatic data member 的存取操作"改为经由 this 指针来存取:
    Ex:
    {
       return sqrt( this->_x * this->_x +
                    this->_y * this->_y +
                    this->_z * this->z);
    }
     
     3. 将 member function 重新写成一个外部函数。对函数名称进行mangling,使它在程序中成为独一无二的词汇:
    name-mangling的设计,避免某些函数重载或者重写时候的名字重合的尴尬。一般用signature来标记即函数名称+参数数目+参数类型。当然咱们编译器错误消息用的是未经mangled的,而链接器用的是mangled过内部名称。
    Ex:
    extern magnitude__7Point3dFv(register Point3d *const this);
     
    这样函数的转化就完成了,而对每一个调用操作也都必须转换。于是:
    obj.magnitude() >> magnitude__7Point3dFv(&obj);
    ptr->magnitude() >> magnitude__7Point3dFv(ptr);
     
    P144 由于 member function 可以被重载(overloaded),所以需要广泛的mangling手法,以提供独一无二的名称。但是如果声明 extern "C",就会压抑 nonmemeber functions的"mangling"效果.
     
    P147
    如果normalize()是一个virtual member function,那么以下的调用:
    ptr->normalize();
    将会被内部转化为:
    (*ptr->vptr[1])(ptr);
    其中:
    • vptr表示由编译器产生的指针,指向virtual table。它被安插在每一个"声明有(或继承自)一个或多个virtual function"的class object中。事实上其名称也会被"mangled",因为在一个复杂的class派生体系中,可能存在有多个vptrs。
    • 1是virtual table slot的索引值,关联到normalize()函数。
    • 第二个ptr表示this指针。
    P147
    使用 class scope operator 明确调用一个 virtual function,其决议(resolved)方式会和 nonstatic member function一样。
     
    P148
    对于以下调用:
    // Point3d obj;
    obj.normalize()
    如果编译器把它转换为:
    (*obj.vptr[1])(&obj);
    虽然语法正确,却没有必要。因为经由obj调用的函数实体只可以是Point3d::normalize()。所以"经由一个class object调用一个virtual function",这种操作应该总是被编译器像对待一般的nonstatic member function 一样地加以决议(resolved)
    这项优化工程的另一利益是,virtual function的一个inline函数实体可以被扩展(expanded)开来,因而提供极大的效率利益。
     
    P150
    Static member function的主要特性是它没有this指针。以下的次要特性都源于其主要特性:
    • 它不能够直接存取其class中的nonstatic members。
    • 它不能被声明为const,volatile,virtual。
    • 它不需要经由class object才被调用-虽然大部分时候它是这样被调用的。
    "member selection"语法的使用是一种符号上的便利,它会被转化为一个直接的调用操作:
    if(Point3d::object_count()>1)
    如果class object是因为某个表达式而获得的,会如何呢?例如:
    if(foo().object_count()>1)
    这个表达式仍然需要被评估求值(evaluated):
    //这样做的目的是为了保存副作用
    //上面的代码被转化为如下形式
    (void)foo();
    if(Point3d::object_count()>1)
     
    P151
    如果取一个static member function的地址,获得的将是其在内存中的位置,也就是其地址。由于static member function没有this指针,所以其地址的类型并不是一个"指向class member function的指针",而是一个"nonmember 函数指针"。
    Ex
    &Point3d::object_count();
    会得到一个数值,其类型是:
    unsigned int(*) ();
    而不是
    unsigned int (Point3d::*)();
     
    P151
    Static member function由于缺乏this指针,因此差不多等同于nonmember function。它提供了一个意想不到的好处:成为一个callback函数,使我们得以将C++和C-based X Windows系统结合。它们也可以成功地应用到线程(thread)函数身上。
     
    P152
    Virtual function的一般实现模型:每一个class有一个virtual table,内含该class之中有作用的virtual function的地址,然后每个object有一个vptr,指向virtual table的所在。
    为了支持virtual function机制,必须首先能够对于多态对象有某种形式的“执行期类型判断法”(runtime type resolution)。
     
    P152
    在C++中,多态(polymorphism)表示“以一个public base class的指针(或reference),寻址出一个derived class object”的意思。
     
    P154
    识别一个class是否支持多态,唯一的方法就是看看它是否有任何virtual function。
    只要class拥有一个virtual function,它就需要这份额外的执行期信息。
    我们所需要的执行期信息包括:
    1)ptr所指对象的真实类型。这可使我们选择正确的virtual function实体。
    2)virtual function实体位置,以便我们能够调用它。
     
    P155
    首先我们需要在每一个多态的class object身上增加两个members
    1)一个字符串或数字,表示class的类型
    2)一个指针,指向某表格,表格中带有程序的virtual function的执行期地址。
     
    表格中的virtual function地址如何被构建起来?在C++中,virtual function(可经由其class object被调用)可以在编译时期获知,此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌握,不需要执行的任何介入。
    执行期备妥这些函数地址,只完成了一半的工作。另一半就是找到这些地址。
    1)为了找到表格,每一个class object被安插上一个由编译器内部产生的指针,指向该表格。
    2)为了找到函数地址,每一个virtual function被指派一个表格索引值。
    这些工作都由编译器完成,执行期要做的,只是在特定的virtual table solt(记录着virtual function的地址)中激活virtual function。
     
    P155
    一个class只会有一个virtual table。每一个table内含其对应的class object中所有的active virtual function函数实体的地址。这些active virtual function包括:
    • 这个class所定义的函数实体。它会改写(overriding)一个可能存在的base class virtual function函数实体。
    • 继承自base class的函数实体。这是在derived class决定不改写virtual function时才会出现的情况。
    • 一个pure_virtual_call()函数实体,它既可以扮演pure virtual function的空间保卫者角色,也可以当作执行期异常处理函数(有时候会用到)。
     
    P156
    每一个virtual function都被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的virtual function的关联。
     
    P158
    当继承发生时,有下面三种情况会发生:
    1)它可以继承base class所声明的virtual function的函数实体。正确地说,该函数实体的地址会被拷贝到derived class的virtual function相对应的slot之中。
    2)它可以使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。
    3)它可以加入一个新的virtual function。这时候virtual function的尺寸会增大一个slot,而新的函数实体地址会被放进该slot之中。
     
    P159
    现在,如果有这样的式子
    ptr->z();
    那么,我们如何有足够的知识在编译时期设定virtual function的调用呢?
    1) 一般而言,我们并不知道ptr所指对象的真正类型,然而我们知道,经由ptr可以存取到该对象的virtual table
    2) 虽然我们不知道哪一个z()函数实体会被调用,但我们知道每一个z()函数地址都被放在slot4
    这些信息使得编译器可以将该调用转化为:
    (*ptr->vptr[4])(ptr);
    在这个转化中,vptr表示编译器所安插的指针,指向virtual table,4表示z()被赋值的slot编号(关联到类继承体系的virtual table)。唯一一个在执行期才能知道的东西是:slot4所指的到底是哪一个z()函数实体。
     
    在一个单一继承体系中,virtual function机制的行为十分良好,不但有效率而且很容易塑造出模型。
     
    P174
    指向 Member Function 的指针
    取一个 nonstatic data member 的地址,得到的结果是该member在class布局中的bytes位置(再加1)。它是一个不完整的值,需要被绑定于某个class object的地址上才能够被存取。
    取一个nonstatic member function 的地址。如果该函数是 nonvirtual, 则得到的结果是它在内存中真正的地址。这个值也是不完全的。它需要被绑定于某个class object的地址上,才能够通过它调用该函数。 所有的 nonstatic member functions 都需要对象的地址(以参数this指出)。
    本文版权归作者 kanego 和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    Electron应用使用electron-builder配合electron-updater实现自动更新(windows + mac)
    Electron 桌面应用打包(npm run build)简述(windows + mac)
    Electron 打包Mac安装包代码签名问题解决方案Could not get code signature for running application
    安装SQL Server 2012过程中出现“启用windows功能NetFx3时出错”(错误原因、详细分析及解决方法)以及在Windows Server2012上安装.NET Framework 3.5的详细分析及安装过程
    SQL Server 2012 Enterprise Edition安装过程详解(包含每一步设置的含义)
    Wmic获取Windows硬件资源信息
    基于Mint UI和MUI开发VUE项目一之环境搭建和首页的实现
    GitHub常用命令及使用
    webpack安装,常见问题和基本插件使用
    vue,react,angular三大web前端流行框架简单对比
  • 原文地址:https://www.cnblogs.com/kanego/p/2268627.html
Copyright © 2020-2023  润新知