原标题:成员函数指针的一些奇怪行为

指向成员函数的指针的怪异行为

提示:以下内容仅仅是针对Microsoft Visual C++编译器产生的行为进行介绍,其他的编译器可能并没有下文所描述的行为,所以,不要搞错了。

如果你只是使用单继承,则指向成员函数的指针实际上是指向了这个函数的起始地址,因为在单继承中,所有基类都共享了同一个this指针。我们看看下面的代码:

因为它们都使用了同一个this指针,一个指向基类成员函数的指针可以被当做是指向Derived2的成员函数指针来使用,不需要进行任何的转换操作。

在单继承中,指向一个类的成员函数指针的大小就是该平台指针的大小。

但是如果你使用到了多重继承,则事情就开始变得有趣起来:

这个时候,就会存在两个可能的this指针。第一个(p)可以被子类Derived和基类Base1使用,第二个(q)可以被子类Base2使用。

一个指向Base1的成员函数的指针可以被当做一个指向Derived的函数指针来使用,因为它们都使用了相同的this指针。

但是,一个指向Base2的成员函数指针就不能被当做一个指向Derived的成员函数指针来使用,因为这个时候,this指针需要做一个微小的移动。

要解决这个问题,有很多不同的方法。Visual Studio编译器是这样处理的。

指向一个多重继承的子类的成员函数指针,实际上是被定义到了一个结构体中,如下图所示:

一个多重继承的子类的成员函数指针的大小为指针的大小加上一个size_t的大小。

如果我们比较多重继承和单继承,则你会发现:成员函数指针的大小会随着具体类对象的不同而不同。

为了通过成员函数指针来调用成员函数,我们需要调整this指针,调整之后才能正确地调用这个成员函数。下面是具体的一个例子:

在上面的代码中,adjustor在什么时候会为非零值呢?

考虑上面提到的例子。成员函数Derived::Base2Method()实际上也等同于Base2::Base2Method(),所以它会期望接收q作为它的this指针。为了将p转换为q,adjustor必须知道sizeof(Base1)的值,所以当Base2::Base2Method()的第一行执行的时候,它会收到预期的q作为它的this指针。

“那,为什么不简单使用一个thunk,这样就不用手动地调整指针了?” 如下图所示:

然后使用这个thunk作为函数指针。

原因在于:存在函数指针的转换。

让我们考察如下的代码:

我们从一个指向Base2成员函数的指针开始,Base2是仅使用单一继承的类,因此它仅包含一个指向代码的指针。要将其分配给使用多个继承的成员函数Derived的指针,我们可以重用函数地址,但是现在我们需要一个adjustor,以便可以将指针“p”正确地转换为“q”。

请注意,该代码不知道pfnBase2指向什么函数,因此不能仅将其替换为匹配的thunk。它必须在运行时生成一个thunk,并以某种方式来决定何时可以安全地释放内存。(因为这是C++。这里没有垃圾收集器。)

还要注意,当pfnBase2强制转换为Derived成员函数的指针时,其大小发生了变化,因为它从仅使用单继承的类的指针变为使用多继承的类的函数的指针。

也即:对一个函数指针进行转换,可能会改变其大小。

我猜在看到这篇文章之前,你不知道有这回事儿吧?

课后练习题

考察下面的类:

下面的代码将会如何编译?

最后

Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。

本文来自:《Pointers to member functions are very strange animals》

相关文章