• 【C++对象模型】第四章 Function 语意学


    1、Member的各种调用方式

    1.1 Nonstatic Member Functions

      实际上编译器是将member function被内化为nonmember的形式,经过下面转化步骤:

      1.给函数添加额外参数——this。

      2.将对每一个nonstaitc data member的存取操作改为this指针来存取。

      3.将member function 重写成一个外部函数。对函数名精选mangling 处理,使之成为独一无二的语汇。

    class A{
    public:
        int x, y;
        int func();
    }
    
    int A::func(){ return x+y; }      //Nonstatic Member function
    int A::func(A *const this)        //1.添加额外参数this指针
    { return this->x + this->y;}     //2.改为由this指针存取成员数据
    
    extern int func_1AFv(A *const this) //3.对函数名进行“mangling”修饰
    
    obj.func() ---> func_1AFv(&obj)

    1.2 Virtual Member Functions

      将 ptr->f();   //f()为virtual member function 内部转化为 (*ptr->vptr[1])(ptr); 其中:

      vptr表示编译器产生的指针,指向virtual table。它被安插在每一个声明有(或继承自)一个或多个virtual functions 的class object 中。

      1 是virtual table slot的索引值,关联到normalize()函数,此处表示第一个函数。

      第二个ptr表示this指针

    //明确的调用可以抑制虚拟机制
    A::func()
    // 通过该obj调用不支持多态
    obj.func() ---> func_1AFv(&obj)

      通过class object调用virtual function的操作会被编译器转换为一般的nonmember member function,不呈现多态。

    1.3 Static Member Functions

    • 参数没有this
    • 不能被声明为 const volatile 或virtual。
    • 不需要经过object 调用,可以使用class直接去调用 
    • 不能够直接存取其class中的非static member ,在static的作用域范围内,要想访问非static的数据成员,就得使用this指针

      Static Member Function会被编译器转化为nonmember形式,实际上该类函数差不多等同于nonmember function。

      一个static member function 会提出于class声明之外,并给予一个经过mangling的适当名称。如果取一个static member function 的地址,获得的是其在内存的位置也就是地址,而不是一个指向“class member function”的指针,如下: 

      &Point::count();  会得到一个数值,类型是:unsigned int(*)(); 而不是:unsigned int(Point::*)();

    static int A::func()   //静态成员函数
    //各种方式调用静态函数,均转换成通过类作用于直接调用
    ptr->func()/obj.func()/A::func() --> A::func()
    //函数mangling修饰
    A::func() ---> func_1ASFv()  //SFv表示静态成员函数,参数void

    2、Virtual Member Funcitons

      C++中,多态表示以“一个public base class 的指针(或reference),寻址出一个derived class object”。

    2.1 编译期间完成的事情 

    • 给每一个class object安插由编译器内部产生的vptr
    • 给vtbl每一个virtual function一个固定的slot,在整个继承体系中保持与特定virtual funciton相联。这些active virtual function 包括:

        这个class 所定义的函数实体(改写(overriding)一个可能存在的base class virtual function函数实体。

        继承自base class 的函数实体(不被derived class改写)
        一个pure_virtual_calle()

    2.2 一个类继承函数virtual table的三种可能

      1.继承base class 所声明的virtual functions的函数实体。正确地说,是该函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。

      2.使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。

      3.可以加入一个新的virtual function。这时候virtual table 的尺寸会增大一个slot放进这个函数实体地址。

      2.3 单一继承

      下面一个类Point的定义:

    class Point {
    public:
        virtual ~Point();   //slot 1
        virtual Point& mult (float) = 0   //slot 2
        float x() const {return _x;}
        virtual float y() const {return 0;}   //slot 3
        virtual float z() const {return 0;}   //slot 4
    protected:
        Point (float x = 0.0);
        float _x;   
    }
    
    class Point2d:public Point {
    public:
        ~Point2d();   //slot 1
        Point2d& mult (float)   //函数覆盖 slot 2
        float y() const {return _y;}   //函数覆盖 slot 3
    protected:
        float _y;   
    }
    
    class Point3d:public Point2d {
    public:
        ~Point3d();   //slot 1
        Point3d& mult (float)   //函数覆盖 slot 2
        float z() const {return _z;}   //函数覆盖 slot 4
    protected:
        float _z;   
    }
    
    //一般来说我们事先并不知道ptr所指对象真正类型
    //所以不知道哪个mult实体会被调用,但mult()固定在slot 2中是确定的
    ptr->mult()
    //因此编译器如下转换
    //执行期唯一需要做的就是确定哪一个slot 2的实体
    (*ptr->vptr[2])(ptr) 

      Point2d继承自Point。Point3d继承自Point2d。那么内存模型如图:

      

      编译时期设定virtual function的调用:

      一般而言,我并不知道ptr 所指对象的真正类型。然而可以经由ptr 可以存取到该对象的virtual table。

      虽然我不知道哪个Z()函数实体被调用,但知道每一个Z()函数地址都被放置slot 4的索引。

      这样我们就可以将ptr->z();转化为:(*ptr->vptr[4])(ptr);

      唯一一个在执行期才能知道的东西是:slot4所指的到底是哪一个z()函数实体

    2.4 多重继承下的 Virtual Functions

      多重继承下,通常派生类会有多个virtual table ,最左边基类的称之为:“主要表格”,第二或更过多基类的表格称为:“次要表格”(参考上图),派生类的主要表格和次要表格可以连在一起,比如Sun的编译器的策略就是这样的。

    class Base1{                                   class Base2{  
    public:                                        public:  
         Base1();                                      Base2();  
         virtual ~Base1();                             virtual ~Base2();  
         virtual void SpeakClearly();                  virtual void mumble();  
         virtual Base1* clone() const;                 virtual Base2* clone()const;  
    };                                             };  

      这两个类我故意并列在一起,Base1和Base2的区别就是两个不同的虚函数void SpeakClearly()void mumble();

        class Derived: public Base1,public Base2{  
        public:  
          Derived();  
           virtual ~Derived();  
           virtual Derived* clone()const;  
        protected:  
           float data_derived;  
        };  

      那么这几个类的virtual table的布局如下:

      

      

      

      多重继承下:derived类会分别重写“主要表格”和“次要表格

    2.5 虚拟继承下的虚函数

      

    3、函数的效能

      nonmemeber、static member或nonstatic member函数都被转换为完全相同形式,所以三者效率完全相同。

    4、指向Member Function的指针

    4.1 nonstatic data member

      取一个nonstatic data member的地址,得到的结果是该member在class 布局中的bytes位置,所以它需要绑定于某个class object的地址上,才能够被存取。

    4.2 nonvirtual function指针

      得到真实地址,但它需要绑定与某个class object上,才能通过它调用。

    double (Point::*coord) = &Point::x;  //nonvirtual function
    //调用函数
    (obj.*coord) ()
    //被编译器转化
    (coord)(&obj)  //&obj为this指针

      static member function函数地址为普通函数指针,因为没有this指针。

    4.3 virtual function指针

      virtual function地址在编译时期是未知的,但是与之相关的vtbl slot的索引值是固定的。所以取virtual function地址只能获得其索引值。

    class Point{
    public:
        virtual ~Point();   //slot 1
        virtual float z();  //slot 2
    }
    
    float (Point::*pmf) = Point::z;   //= 2
    Point *ptr = new Point2d;
    ptr->*pmf;
    //被编译器转化为
    (*ptr->vptr[(int)pmf](ptr)); 

    4.4 多重继承下 member function指针

      问题:如何区别Member Function指针指向地址还是virtual table索引? 
      方法:修改Member Function指针mptr结构体,mptr->index正负判别。

    struct __mptr {
        int delta;
        int index;  //virtual function时:vtbl索引 nonvirtual时为-1
        union{
            ptrtofunc faddr;   //nonvirtual 时函数地址
            int       v_offse;
        }
    }

    5、Inline Funcitons

      inline函数参数带有副作用,或者是以一个单一的表达式做多重调用,或是inline函数中有多个局部变量,都会产生临时对象,可能会产生大量的扩展码,是程序的大小膨胀,所以inline函数的使用必须要谨慎。

    5.1 对于单一表达式的多重调用

      对于如下incline函数:

        inline Point operator+ (const Point& lhs, const Point& rhs)  
        {  
            Point new_pt;  
            new_pt.x(lhs.x() + rhs.x());//x()函数为成员变量_x的get,set函数  
            return new_pt;  
        }  

      由于该函数只是一个表达式,在cfront中,则其第二或者后继的调用操作不会被扩展,会变成:

     new_pt.x = lhs._x + x__5PointFV(&rhs); 

      没有带来效率上的提升,需要重写为:

        new_pt.x(lhs._x + rhs._x); 

    5.2 inline函数对于形式(Formal)参数的处理

      有如下inline函数:

        inline int min(int i, int j)  
        {  
            return i < j ? i : j;  
        } 

      如下三个调用:

        inline int bar()  
        {  
            int minVal;  
            int val1 = 1024;  
            int val2 = 2048;  
          
            minVal = min(val1, val2);//case 1  
            minVal = min(1024, 2048);//case 2  
            minVal = min(foo(), bar() + 1);//case 3  
              
            return minVal;  
        }

      对于case1,调用会直接展开:

        minVal = val1 < val2 ? val1 : val2;

      case2直接使用常量:

        minVal = 1024;

      对于case3,由于引发了参数的副作用,需要导入临时对象,以避免重复求值(注意下面逗号表达式的使用):

        int t1;  
        int t2;  
          
        minVal = (t1 = foo()), (t2 = bar() + 1), t1 < t2 ? t1: t2; 

    5.3 inline函数中引入了局部变量

      改写上述min函数,引入一个局部变量:

        inline int min(int i, int j)  
        {  
            int minVal = i < j ? i : j;  
            return minVal;  
        }  

      对于如下调用:

        int minVal;  
        int val1 = 1024;  
        int val2 = 2048;  
        minVal = min(val1, val2); 

      为了维护局部变量,会被扩展为:

        int minVal;  
        int val1 = 1024;  
        int val2 = 2048;  
          
        int __min_lv_minVal;//将inline函数局部变量mangling  
        minVal = (__min_lv_minVal = val1 < val2 ? val1: val2), __min_lv_minVal;  

      再复杂一些的情况,例如局部变量加上有副作用的参数,会导致大量临时性对象的产生:

        minVal = min(val1, val2) + min(foo(), bar() + 1); 

      会被扩展成为,注意逗号表达式,由左至右计算各个分式,以最右端的分式值作为最终值传回:

        int __min_lv_minVal_00;  
        int __min_lv_minVal_01;  
          
        int t1;  
        int t2;  
          
        minVal = (__min_lv_minVal_00 = val1 < val2 ? val1 : val2, __min_lv_minVal_00) +  
        (__min_lv_minVal_01 = (t1 = foo(), t2 = bar() + 1, t1 < t2 ? t1 : t2), __min_lv_minVal_01); 

      会产生多个临时变量,所以inline函数的使用必须要谨慎。

  • 相关阅读:
    php解析word,获得文档中的图片
    小程序 图表 antv f2 的使用
    eslint配置大全
    node 操作word excel
    vue-element-admin
    python中字符串前的r什么意思
    python3 三种字符串(无前缀,前缀u,前缀b)与encode()
    Markdown语法
    Python3 字符串前面加u,r,b的含义
    Python os.walk()
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/6754045.html
Copyright © 2020-2023  润新知