今天在MFC工程中使用了WM_USER定义用户消息,从文档中得知ON_MESSAGE的消息处理函数应该符合如下格式:afx_msg LRESULT (CWnd::*)(WPARAM, LPARAM)。其中的作用域"CWnd::"引起了我的好奇。我们知道,消息处理函数只能在CWnd的派生类中定义或者重载,其作用域必然是CWnd派生类。为此我做了个实验:
class Parent
{
};
class Child: public Parent
{
public:
int Func(int i, int j){return 0;}
};
int main()
{
int (Parent::*pointer)(int, int);
pointer = &Child::Func;
return 0;
}
不能通过编译。以基类为作用域的函数指针不可以接管派生类的成员函数。将代码修改为:
class Parent
{
public:
int Func(int i, int j){return 0;}
};
class Child: public Parent
{
};
int main()
{
int (Child::*pointer)(int, int);
pointer = &Parent::Func;
return 0;
}
顺利通过编译。以派生类为作用域的函数指针可以接管基类的成员函数。推测其原因如下:类的成员函数会接受该类的this指针作为隐藏参数。Child类对象可以提供Child类的this指针给Parent::Func(),使用派生类的指针为基类的指针赋值是安全的,因此编译器允许这种行为。但是使用基类的指针为派生类的指针赋值是不安全的,因此编译器不允许以基类为作用域的函数指针接管派生类的成员函数。
但是在MFC中我们却看到了以CWnd::为作用域的函数指针接管在CWnd派生类的成员函数,这跟我们的实验结果刚好相反。让我们来看ON_MESSAGE的定义:
#define ON_MESSAGE(message, memberFxn) \
{ message, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW) \
(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > (memberFxn)) },。
原来,CWnd派生类的成员函数在传递进ON_MESSAGE()之后进行了类型转换,其作用域变成了CWnd::,自然可以被作用域为CWnd::的函数指针接管。如果没有这一步操作,消息处理函数根本进入不了映射表。
最后,让我们通过例子来说明如何调用这个函数指针:
#include <iostream>
using namespace std;
class Parent
{
};
class Child: public Parent
{
public:
Child():m_Val(26) {}
void Func() {cout<<m_Val<<endl;}
private:
int m_Val;
};
int main()
{
void (Parent::*pointer)();
pointer = (void (Parent::*)())(&Child::Func);
Child child;
Parent* pParent = &child;
(pParent->*pointer)();
return 0;
}
这个例子首先让一个作用域为Parent::的函数指针pointer接管了Child::Func,然后让一个Parent指针调用pointer。运行结果是:屏幕上正确输出m_Val的值26。pointer知道Child::m_Val的存在!大家想想,如果pParent指针不是指向一个Child类对象,而是指向本类对象,会发生什么样的后果?就会发生越界访问。因此,编译器禁止以基类为作用域的函数指针接管派生类的成员函数。
MFC开发小组的大牛们利用巧妙的方式绕过了这个规则。但是严谨的规则可以帮助你不会轻易出错。