C++对象模型剖析(八)一一Function语义学(一)

Function语义学(一)

C++支持三种类型的 function:

  • nonstatic member function
  • static member function
  • virtual member funcion

Member 的各种调用方式

  • Nonstatic Member Funcions非静态成员函数

    C++的设计准则之一就是:**nonstatic member funcion 至少必须和一般的 nonmember funcion 有相同的效率。**也就是说调用 nonstatic member function 不应该带来额外的负担。

    float magnitude3d ( const Point3d *_this ) { ... }
    float Point3d::magnitude3d () const { ... }
    
    // 假设magnitude3d的定义
    float magnitude3d ( const Point3d *_this)
    {
        return sqrt( _this->_x * _this->_x + 
                     _this->_y * _this->_y +
                   	 _this->_z * _this->_z );
    }
    

    直接这样看到话,好像非静态成员函数更加有效率,因为它们省去了寻址的过程。但是,实际上,非静态成员函数在编译器内部就是转化为了非成员函数的定义。我们看看编译器是如何将非静态成员函数进行转化的吧。

    1. 改写函数签名(signature,书上叫做函数原型,但是我觉得叫函数签名比较好听,因为很多其他书上也是这么叫的),并安插一个额外的参数到成员函数中,用以提供一个管道,使 class object 得以将此函数调用。该额外参数称为 this 指针。(执行期,this指针会自动地跟一个class object 进行绑定)。

      Point3d
      Point3d::magnitude(Point3d *const this) { ... }
      Point3d
      Point3d::magnitude(const Point3d *const this) { ... }
      
    2. 将每一个 “ 对 nonstatic data member 的存取操作” 改为经由 this 指针来存取。

      {
          return sqrt(
          		this->_x * this->_x +
          		this->_y * this->_y +
          		this->_z * this->_z);
      }
      
    3. 将 member function 重新写成一个外部函数。该函数名经过 “ mangling ” 处理,使它在程序中成为独一无二的语汇:

      extern magitude__7Point3dFv (
      	register Point3d *const this);
      
    4. 将每一个调用操作进行转换

      obj.magintude();
      magnitude__7Point3dFv(&obj);
      ptr->magintude();
      magintude__7Point3dFv(ptr);
      

    我们看看当返回值有类型的时候是怎么重写的,其实这在之前的章节中都有提到。

    Point3d Point3d::normalize()
    {
        register float mag = magnitude();
        Point3d normal;
        
        normal._x = _x/mag;
        normal._y = _y/mag;
        normal._z = _z/mag;
        
        return normal;
    }
    
    float Point3d::magnitude() const
    {
        return sqrt(_x * _x + _y * _y + _z * _z);
    }
    
    // 编译器内部转化
    void normalize_7Point3dFv( register const Point3d *const this, Point3d &__result)
    {
        register float mag = this->magnitude();
        
        // default constructor
        _result.Point3d::Poind3d();
        
        _result._x = this->_x/mag;
        _result._y = this->_y/mag;
        _result._z = this->_z/mag;
        
        return;
    }
    

    现在我们看看的书上说的一个比较有效率的方法(但是我没看出来,可能是因为不太了解构造函数吧哈哈,也可能是构造函数里面用到了是初值化列表initialize table

    Point3d Point3d::normalize() const
    {
        register float mag = magnitude();
        return Point3d(_x/mag, _y/mag, _z/mag);
    }
    
    // 看看编译器是怎么进行内部转换的
    void
    normalize_7Point3dFv( register const Point3d *const this,
                        	Point3d &_result)
    {
        register float mag = this->magnitude();
        _result.Point3d::Point3d(_x/mag, _y/mag, _z/mag);
        return;
    }
    

    书:这可以节省默认构造函数所引起的负担(有道理)。

  • 名称的特殊处理 (Name Mangling)

    一般而言,member 的名称前面会被加上 class 名称,形成独一无二的命名。

    class Bar { public: int ival; ... }
    // 经过 name mangling 之后
    int ival_3Bar;
    

    为什么这么做呢?看个例子就清楚了。

    class Foo : public Bar { public: int ival; ...}
    // 现在在一个Foo类中,有两个同名的变量,编译器需要对它们进行区分
    // 我们对不同类变量的区分是这样的
    int Foo::ival;
    int Bar::ival;
    // 但是编译器往往采用最简单的方法--改名-> name mangling
    class Foo : public Bar
    {
    public:
        int ival_3Foo;
        int ival_3Bar;
    }
    

    众所周知,类成员包括成员变量和成员函数,现在我们就来看看成员函数。

    由于函数可以被重载化,所以编译器需要更广泛的 mangliing 手法。

    看例子:

    class Point {
    public:
        void x( float newX);
        float x();
        ...
    };
    
    // 如果转换为
    class Point {
    public:
        void x_5Point(float newX);
        float x_5Point();
        ...
    }
    

    这样的话就会有两个函数拥有相同的名字,但是一般情况下,也是不会出错的,因为它们的函数签名并不相同。但是为了制造出独一无二的效果,只有将它的参数列表也加进到编码中。

    但是如果你声明了 extren C,就会压抑 nonmember functions 的 “mangling” 效果。

  • virtual menber functions 虚拟成员函数

经过前面的学习,相信大家对 virtual member function 和 virtual function table 都不陌生了吧。但是书上有的该写的还是得写,唉。

// 如果这是个 virtual member function
ptr->normalize();
// 那么在编译器内部就会转化为
(* ptr->vptr[1])(ptr);
  • vptr 表示由编译器产生的指针,指向 virtual function table(通过之前的学习,我们知道,可能 virtual function table 中不一定只有virtual function pointer,可能在负索引是 virtual base class 的 offset)。它被安插在每一个“声明有(或继承自)一个或多个 virtual functions ”的 class object 中。事实上, 它的名称也可能被 “mangling” 因为在一个复杂的 class 派生体系中,可能存在多个 vptr。
  • 1 表示 virtual table slot 的值(由编译器在编译器决定的,关联到对应的函数。
  • 第二个 ptr 表示 this 指针。

现在我们看看之前的例子:如果 normalize()也是一个 virtual member function

// register float mag = magnitude()
register float mag = (* this->vptr[2])(this);

这个时候,由于Point3d::magnitude()是在Point3d::noralize()中被调用的,而后者已经由虚拟机制而决议(resolve)妥当,所以直接显式调用Point3d实例会更有效率,并因此压制了由于虚拟机制而产生的不必要的重复操作:

register float mag = Point3d::magnitude();

如果magnitude()声明为 inline 函数,会更有效率。使用clas scope operator显式调用一个 virtiual function,其决议方式会和 nonstatic member function 一样。

Point3d obj;
obj.normalize();
// change
normalize_7Point3dFv( &obj );
// 并不需要通过 vptr 对 virtual member function 进行访问

书:编译器的这种优化工程的另一个利益是,virtual function 的一个 inline 函数实例可以被扩展开来(expanded),因而提供极大的效率利益。(后面会讲到)

  • Static Member Functions (静态成员函数)

    在引入 Static Member Function 之前,C++语言要求所有的 member function 都必须经由该 class 的 object 调用。而实际上,只有当一个或多个 nonstatic data members 在member function 中被直接存取时,才需要 class object。Class object 提供了 this 指针给这种形式的函数调用使用。这个 this 指针把 “在 member function 中存取的 nonstatic class members” 绑定于 “object 内对应的 members ” 之上。如果没有任何一个 members 被直接存取,事实上就不需要 this 指针,因此也就没有必要通过一个 class object 来调用一个 member function。但是C++语言到目前为止并不能辨识这种情况。

    这么一来就在存取 static data members 产生了一些不规则性。如果 class 的设计者把 static data member 声明为 nopublic(好习惯),那么他就必须提供一个或多个 member function 来存取该 member。因此,虽然你可以不靠 class object 来存取一个 static member,但是其存取函数必须绑定一个 class object。

    所以。。。

    下面介绍一个 static member function 的特性:

    • 它不能够直接存取其 class 中的 nonstatic members。
    • 它不能够被声明为 const,volatile 或 virtual。
    • 它不需要经由 class object 才被调用。

    下面再看看一些需要注意的小细节

    // 先把例子定义好
    unsigned int Point::object_count ()
    {
        return _object_count;
    }
    
    1. 首先,static member function 也是会被 mangling 的。

    2. 对一个 static member function 取地址,得到的类型是一个 nonmember function pointer,而不是一个 class member function pointer。

      auto p = &Point3d::object_count();
      // p -> unsigned int (*)()
      // 而不是 
      // unsigned int (Point3d::*)()
      
    3. 由于缺乏 this 指针,因此差不多等同于 nonmember function。所以,它可以成为一个 callback 函数(回调函数)。

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值