小言_互联网的博客

C++(对象模型):08---Function之(Function Member的各种调用方式(静态函数、非静态函数、虚函数)、附C++的mangling机制)

370人阅读  评论(0)

前言

  • virtual函数是在20世纪80年代中期被加进来的,并且一开始受到了很多的之一
  • 静态成员函数时最后被引入的一种函数类型。它们在1987年的Usenix C++研讨会的厂商研习营中被正式提议加入C++中

一、非静态成员函数

  • C++的设计准则之一就是:非静态成员函数至少必须和一般的非成员函数有相同的效率

演示说明

  • 如果我们相对类对象进行操作,那么以下两个函数的调用都是相同效率的,因为编译器内部已将“成员函数实体”转换为对等的“非成员函数实体”

   
  1. float magnitude3d(const Point3d *_this) {...} //非成员函数版本
  2. float Point3d::magnitude3d() {...} //成员函数版本
  • 如果是非成员函数版本:我们在函数内对类对象进行了如下操作

   
  1. float magnitude3d(const Point3d *_this)
  2. {
  3. return sqrt(_this->_x * _this->_x+
  4. _this->_y * _this->_y+
  5. _this->_z * _this->_z);
  6. }
  • 如果是成员函数版本:虽然我们没有传入对象指针,但是成员函数会默认包含this指针,因此成员函数可能会被编译器进行如下的转化

   
  1. /*第一步:
  2. 编译器的增长过程:自动增加了this指针
  3. 备注:如果函数为const类型,那么参数就会变为const Point3d *const this
  4. */
  5. float Point3d::magnitude3d(Point3d * const this)
  6. {
  7. //通过this指针进行操作
  8. return sqrt( this->_x * this->_x+
  9. this->_y * this->_y+
  10. this->_z * this->_z);
  11. }
  12. /*第二步:
  13. 接着将成员函数改写为一个外部函数,并使用C++的“mangling机制”对函数的名称进行处理,
  14. 使函数名在程序中是独一无二的
  15. */
  16. extern magnitude_7Point3dFv(register Point3d *const this);
  17. int main()
  18. {
  19. //因此对成员函数的调用的转换将如下所示
  20. Point3d obj,*ptr;
  21. obj.magbitude(); //将会转换为magnitude_7Point3dFv(&obj);
  22. ptr->magbitude(); //将会转换为magnitude_7Point3dFv(prt);
  23. }

二、名称的特殊处理(mangling机制)

  • 规则:成员的名称后面会被加上class名称,形成独一无二的名称
  • 下面我们介绍的编码方法是cfront采用的。目前编译器并没有统一的编码方法

成员变量演示说明

  • 例如Bar类中的ival成员可能会被翻译为ival_3Bar

   
  1. class Bar {
  2. public:
  3. //被翻译为ival_3Bar
  4. int ival;
  5. };
  • 编译器这么做,是为了在派生中,子类覆盖了父类的成员,防止名称冲突,请看下面说明

   
  1. class Bar {
  2. public:
  3. int ival;
  4. };
  5. class Foo : public Bar {
  6. public:
  7. //覆盖了父类的ival,此ival被翻译为ival_3Foo
  8. int ival;
  9. //父类的ival在子类中为ival_3Bar,因此不会产生冲突
  10. };

成员函数演示说明


   
  1. class Point {
  2. public:
  3. void x() { float newX; }
  4. float x() {}
  5. };
  • 例如下面两个成员函数是重载的,被编译器翻译为两个不同名称的函数 

   
  1. class Point {
  2. public:
  3. void x_5Point() { float newX; }
  4. float x_5Point() {}
  5. };
  • 如果再把它们擦参数也加上去,就一定可以制造出独一无二的结果了(但如果你声明extern "C",就会压抑非成员函数的“mangling”效果)

   
  1. class Point {
  2. public:
  3. void x_5PointFf() { float newX; }
  4. float x_5PointFv() {}
  5. };

三、虚拟成员函数


  
  1. class Point3d {
  2. public:
  3. virtual Point3d normalize()const {
  4. register float mag = magnitude();
  5. Point3d narmal;
  6. narmal._x = _x / mag;
  7. narmal._y = _y / mag;
  8. narmal._z = _z / mag;
  9. return narmal;
  10. }
  11. virtual float magnitude()const {
  12. return sqrt(_x*_x + _y*_y + _z*_z);
  13. }
  14. protected:
  15. float _x,_y, _z;
  16. };

演示说明

  • 因为normalize()是一个虚函数,所以对其的调用,将会进行如下代码的转换,其中:
    • vptr:由编译器产生的指针,指向虚函数表
    • 1:是虚函数表中的索引值,关联到normalize()加密手机
    • 第二个ptr:表示this指针

   
  1. Point3d *ptr;
  2. ptr->normalize();
  3. //被内部转换为
  4. (*ptr->vptr[ 1])(ptr);
  • 类似的道理,magnitude虚函数的调用将会进行如下转换:

   
  1. class Point3d {
  2. public:
  3. virtual Point3d normalize()const {
  4. //...
  5. register float mag = magnitude();
  6. //此语句会被翻译为register float mag=(*this->vptr[2])(this);
  7. //...
  8. }
  9. virtual float magnitude()const { //...}
  10. protected:
  11. //...
  12. };
  • 抑制虚拟机制:由于magnitude()是在normalize()中被调用,而后者已经由虚拟机制而决议(resolved)妥当,所以明确地调用“Point3d实体”会比较有效率,并因此压制由于虚拟机制而产生的不必要的重复调用操作

   
  1. virtual Point3d normalize()const {
  2. //register float mag = magnitude();
  3. //明确的调用操作会压制虚拟机制
  4. register float mag = Point3d::magnitude();
  5. //...
  6. }
  • 如果magnitude()声明为inline函数会更有效率,使用类作用域操作符明确调用一个虚函数,其决议方式会和非静态成员函数一样

   
  1. virtual Point3d normalize()const {
  2. register float mag = Point3d::magnitude();
  3. //被翻译为register float mag = magnitude_7Point3dFv(this);
  4. //...
  5. }
  • 对于调用naomalize()会被编译器进行如下缓缓

   
  1. Point3d obj;
  2. obj.naomalize();
  3. //会被转换为:
  4. (*obj.vptr[ 1])(&obj);
  • 虽然语义正确,却没有必须。请回忆哪些并不支持多态的对象。所以上述经由obj调用的函数实体只可以是Point::naomalize()“经由一个类对象调用虚函数”,这种操作应该总是被编译器像对待一般的非静态成员函数一样地加以决议
normalize_7Point3dFv(&obj);
  • 这项优化工程的另一个利益是,虚函数的一个内联函数实体可以被扩展开来,因而提供极大的效率利益

四、静态成员函数

静态成员函数的调用转换

  • 如果Point3d::normalize()是一个静态函数,以下两种调用操作将会被转换为一般的非成员函数调用

   
  1. obj.normalize();
  2. ptr->normalize();
  3. //以上两种会被转换为非成员函数的调用
  4. normalize__7Point3dSFV(); //obj.normalize();
  5. normalize__7Point3dSFV(); //ptr->normalize();
  • 在C++引入静态成员函数之前,很好看到有如下的调用方式,这种调用方式只是简单调用normalize这个静态函数
((Point3d*)0)->normalize();

静态成员函数的特点

  • 没有this指针
  • 不能够直接存取其雷钟德非静态成员
  • 不能被声明为const、volatile或virtual
  • 不需要经由类对象被调用(但可以通过类对象调用)

静态成员函数的地址

  • 如果取一个静态成员函数的地址,获得的将是其在内存中的位置,也就是其地址
  • 由于静态成员函数没有this指针,所以其地址的类型并不是一个“指向类成员函数的指针”,而是一个“非成员函数指针”
  • 也就是说:
&Point3d::object_count();
  • 会得到一个数值,类型是:
unsigned int (*)();
  • 而不是:
unsigned int (Point3d::*)();
  • 静态成员函数由于缺乏this指针,因此差不多等同于非成员函数。它提供了一个好处:成为一个回调(callback)函数

转载:https://blog.csdn.net/qq_41453285/article/details/104028834
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场