考虑以下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。
它是如何发生的?
答案 0 :(得分: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 ,which specifies that。
答案 1 :(得分:10)
这是关于成员函数指针的太多信息。在“表现良好的编译器”下有一些关于虚函数的东西,虽然IIRC当我读到文章时我正在浏览那部分,因为这篇文章实际上是关于用C ++实现委托。
http://www.codeproject.com/KB/cpp/FastDelegate.aspx
简短的回答是它取决于编译器,但一种可能性是成员函数指针实现为包含指向“thunk”函数的指针的结构,该函数进行虚拟调用。
答案 2 :(得分:1)
我不完全确定,但我认为这只是规则的多态行为。我认为&A::f
实际上意味着类的vtable中函数指针的地址,这就是为什么你没有得到编译器错误。 vtable中的空间仍然被分配,这是您实际返回的位置。
这是有道理的,因为派生类基本上用指向其函数的指针覆盖这些值。这就是(a->*f)()
在第二个示例中起作用的原因 - f
引用了派生类中实现的vtable。