28

考虑以下 C++ 代码:

class A
{
public:
      virtual void f()=0;
};


int main()
{
     void (A::*f)()=&A::f;
}

如果我不得不猜测,我会说 &A::f 在这种情况下意味着“A 的 f() 实现的地址”,因为指向常规成员函数和虚拟成员函数的指针之间没有明确的分隔. 由于 A 没有实现 f(),这将是一个编译错误。然而,事实并非如此。

不仅如此。以下代码:

void (A::*f)()=&A::f;
A *a=new B;            // B is a subclass of A, which implements f()
(a->*f)();

实际上会调用 B::f。

它是如何发生的?

4

3 回答 3

22

它之所以有效,是因为标准说这就是它应该发生的方式。我用 GCC 做了一些测试,结果是对于虚函数,GCC 以字节为单位存储了相关函数的虚表偏移量。

struct A { virtual void f() { } virtual void g() { } }; 
int main() { 
  union insp { 
    void (A::*pf)();
    ptrdiff_t pd[2]; 
  }; 
  insp p[] = { { &A::f }, { &A::g } }; 
  std::cout << p[0].pd[0] << " "
            << p[1].pd[0] << std::endl;
}

该程序输出1 5- 这两个函数的虚拟表条目的字节偏移量。它遵循Itanium C++ ABI它指定.

于 2009-07-06T15:38:27.797 回答
10

这里有太多关于成员函数指针的信息。在“The Well-Behaved Compilers”下有一些关于虚函数的内容,尽管当我阅读 IIRC 文章时,我正在略读那部分,因为这篇文章实际上是关于在 C++ 中实现委托。

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

简短的回答是它取决于编译器,但一种可能性是成员函数指针被实现为一个结构,其中包含一个指向进行虚拟调用的“thunk”函数的指针。

于 2009-07-06T15:36:20.513 回答
1

我不完全确定,但我认为这只是常规的多态行为。我认为这&A::f实际上意味着类的 vtable 中函数指针的地址,这就是为什么你没有得到编译器错误的原因。仍然分配了 vtable 中的空间,这就是您实际返回的位置。

这是有道理的,因为派生类本质上用指向它们的函数的指针覆盖了这些值。这就是为什么(a->*f)()在您的第二个示例中有效 -f引用在派生类中实现的 vtable。

于 2009-07-06T15:40:11.163 回答