C++对象模型:g++的实现(六)


1. 成员函数指针

对于静态成员函数,其和常规的函数是一样的,故这里不做介绍。下面主要介绍非静态的成员函数指针,包括普通的非virtual成员函数指针和virtual成员函数指针。
注意,这篇是按照《深度探索C++对象模型》的内容写的,最后讲到支持多继承的成员函数指针时才会给出真正的成员函数指针的实现!

1.1 非virtual成员函数指针

对于一个非virtual的成员函数取址,得到的就是该成员函数在内存中的地址,但是它不能单独调用,需要使用其绑定的对象/指针/引用调用。


 
  // test26.cpp
   
  class Test {
  public:
  Test(int i)
  : m_i(i)
  {}
   
  int getInt() const {
  return m_i;
  }
   
  void setInt(int i) {
  m_i = i;
  }
   
  private:
  int m_i;
  };
   
  int main() {
  Test t(1);
  int i = t.getInt();
  void (Test::*pMemberFunc)(int) = nullptr; // 成员函数指针
  pMemberFunc = &Test::setInt;
  (t.*pMemberFunc)(2);
  i = t.getInt();
  }

1.2 支持“指向虚成员函数”的指针

对于非虚成员函数我们可以直接拿到其地址,因为其没有多态性。但对于虚函数,其地址要在运行时确定,因此对于虚成员函数我们取的应该是其相对虚表指针的偏移index。
所以如果有如下类:


 
  class Point {
  public:
  Point(int x, int y);
  virtual
  ~Point();
   
  int x() const {return m_x;}
  int y() const {return m_y;}
  virtual
  int z() const { return 0; }
  private:
  int m_x;
  int m_y;
  };

对于析构函数取值&Point::~Point取得的是0。
对于x()和y()取址&Point::x, &Point::y得到的是其地址,因为他们不是虚函数。
对于z()取址&Point::z得到的是1。通过pMemberFunc调用z(),其会是类似下面的形式:


 
  (*ptr->vptr[(int)pMemberFunc])(ptr)

1.3 支持多继承的成员函数的指针

在多继承的情况下还要考虑虚函数表的位置问题,因为在多重继承下可能有多个虚函数表;还有this指针可能需要进行偏移,如果派生类没有覆盖第二个或后面的基类的虚函数的话。
为了要支持以上种种特性:如果是非虚函数,指针中要包括其地址;如果是虚函数,要包括其相对虚表指针的偏移;如果是多重继承,还要找到虚函数在哪个虚表中和对this指针进行偏移。
在《深度探索C++对象模型》中提出的是这样的结构:


 
  struct _mptr{
  int delta;
  int index;
  union {
  PtrToFunc faddr;
  int v_offset;
  };
  };

其中delta是this指针要进行的偏移,index是虚函数在虚表指针指向空间中的下标,faddr是非虚函数的地址,v_offset是虚表指针的的位置。
所以下面的操作:


 
  (ptr->*pmf)();

会变成:


 
  // 我觉得这个可能是有问题
  pmf.index < 0
  ? // 非虚函数调用
  (*pmf.faddr)(paddr)
  : // 虚函数调用
  (*ptr->vptr[pmf.index])(ptr)

《深度探索C++对象模型》中是这么写的,但按照作者的说法,实际的代码应该是:


 
  pmf.index < 0
  ?
  (pmf.faddr)(pmf + delta)
  :
  (((vptr*)(ptr+pmf.v_offset))[pmf.index])(ptr+delta)
  // (ptr+pmf.v_offset) 是虚表地址
  // ((vptr*)(ptr+pmf.v_offset))[pmf.index] 是虚表的第pmf.index项
  // ptr+delta是对this指针进行偏移

让我们来看看g++中是怎么实现的:


 
  // test27.cpp
   
  class Point {
  public:
  Point(int x, int y);
  virtual
  ~Point();
   
  int x() const {return m_x;}
  int y() const {return m_y;}
  virtual
  int z() const { return 0; }
  private:
  int m_x;
  int m_y;
  };
   
  Point::Point(int x, int y)
  : m_x(x), m_y(y)
  {}
   
  Point::~Point() {
  m_x = m_y = 0;
  }
   
  int main() {
  Point p(1, 2);
   
  using MemberFunction_t = int (Point::*)() const ;
   
  MemberFunction_t pVirtualMemberFunc = nullptr;
  MemberFunction_t pMemberFunc = nullptr;
   
  pMemberFunc = &Point::x;
  pVirtualMemberFunc = &Point::z;
   
  int x = (p.*pMemberFunc)();
  int z = (p.*pVirtualMemberFunc)();
   
  ++z;
  }

我们使用gdb看一下这个成员函数指针的size:


 
  (gdb) p sizeof(MemberFunction_t)
  $1 = 16

在赋值之后,查看pMemberFunc和pVirtualMemberFunc的二进制是什么:


 
  (gdb) x/2ag &pMemberFunc
  0x7ffffffee0d0: 0x8000a86 <Point::x() const> 0x0
  (gdb) x/2ag &pVirtualMemberFunc
  0x7ffffffee0c0: 0x11 0x0

可以看到g++实现的成员函数指针有两个QWORD(QWORD是size为8字节的【有符号或无符号】整型值)。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/292740.html

(0)
上一篇 2022年11月8日
下一篇 2022年11月8日

相关推荐

发表回复

登录后才能评论