类成员函数指针

类成员函数指针（member function pointer），是C++语言的一类指针数据类型，用于存储一个指定类具有给定的形参列表与返回值类型的成员函数的访问信息。
语法.
使用codice_1声明一个成员指针类型，或者定义一个成员指针变量。使用codice_2或者codice_3调用类成员函数指针所指向的函数，这时必须绑定（binding）于成员指针所属类的一个实例的地址。例如：
<syntaxhighlight lang="cpp">
struct X {
};
void (X::* pmf)(int); //一个类成员函数指针变量pmf的定义
pmf = &X::f; //类成员函数指针变量pmf被赋值
X ins, *p;
p=&ins;
(ins.*pmf)(101); //对实例ins，调用成员函数指针变量pmf所指的函数
(p->*pmf)(102); //对p所指的实例，调用成员函数指针变量pmf所指的函数
</syntaxhighlight>
由于C++运算符优先级列表中，函数调用运算符codice_4的优先级高于codice_2与codice_3，因此成员函数指针所指的函数被调用时，必须把实例对象或实例指针、codice_2或codice_3运算符、成员函数指针用括号括起来，如上例所示。
C++标准规定，非静态成员函数不是左值，因此非静态成员函数不存在表达式中从函数左值到指针右值的隐式转换，非静态成员函数指针必须通过&运算符显式获得。所以上例中，pmf = X::f; 将编译报错。
语义.
不同于普通函数，类成员函数的调用有一个特殊的不写在形参表里的隐式参数：类实例的地址。因此，C++的类成员函数调用使用codice_9调用协议。类成员函数是限定(qualification)于所属类之中的。
同样，类成员函数指针与普通函数指针不是一码事。前者要用.*与->*运算符来使用，而后者可以用codice_10运算符（称为“解引用”dereference，或称“间址”indirection）。普通函数指针实际上保存的是函数体的开始地址，因此也称“代码指针”，以区别于C/C++最常用的数据指针。而类成员函数指针就不仅仅是类成员函数的内存起始地址，还需要能解决因为C++的多重继承、虚继承而带来的类实例地址的调整问题。因此，普通函数指针的尺寸就是普通指针的尺寸，例如32位程序是4字节，64位程序是8字节。而类成员函数指针的尺寸最多有4种可能：
C++标准并没有明确规定类成员指针在派生类与基类之间的类型转换。但不允许类成员函数指针与其它无继承关系的类的成员函数指针互相转换。不允许与普通函数指针互相转换。
如果把基类的虚函数赋给派生类的成员函数指针，例如
<syntaxhighlight lang="cpp">DerivedClass_Func_to_Mem = & BaseClass::virtualFunc;</syntaxhighlight>
实际上是把基类虚表中该虚函数条目对应到了派生类成员函数指针。调用该成员函数指针会执行到哪个函数，需要动态决定。
类成员函数指针可以用0赋值；可以用==运算符、!=运算符。但不允许使用其他的指针算术与比较运算符，如>、<等等。
不能把类的静态成员函数赋值给类成员函数指针。类的静态函数只能赋值给普通函数指针。因为类的静态成员函数不具有this指针，不采用thiscall调用协议，实际上是限定于类作用域的普通函数。 所以，确切地说，应该称“类非静态成员函数指针”。
对于g++编译器，不支持把虚基类的成员函数指针赋给派生类的成员函数指针。也即，g++不支持在虚继承关系下的成员函数指针的upcast。这大大简化了g++成员函数指针的实现难度。g++编译出来的成员函数指针长度都是8字节，其中的高4字节是用于多重继承时调整this指针的偏移值，单继承时该值为0；低4字节是个union结构，对于非虚成员函数就是函数体的内存起始地址，对于虚函数是该函数在虚表（vtable）中的地址字节偏移量再加上1。这是因为，函数体的内存起始地址起码是4字节边界对齐，所以该值是4的的倍数；而虚表中每个条目是4字节长度（对于32位程序），虚函数所对应的虚表条目在虚表中的按字节计算的偏移量也是4的倍数，加上1后就是个奇数。从而可以区分非虚函数与虚函数两种情形。
Microsoft Visual C++编译器支持在虚继承关系下的成员函数指针的upcast。这大大复杂化了该编译器的成员函数指针的实现。Visual C++定义了三个关键字：codice_11分别对应于类是单继承、多重继承、虚继承关系；此外还有第四种情况：类在提前声明（forward declaration）时的未知类型（unknown）成员函数指针。上述四种情况，Visual C++编译出的32位程序的成员函数指针长度分别是4字节、8字节、12字节、16字节。上述3个继承关系关键字用于在类定义时，显式规定该类的成员函数指针的长度及保存在其中的信息类别。如果在一个源文件（编译单元）中在没有一个类的定义的情况下调用了该类的未知类型（unknown）成员函数指针，显然必须在其他源文件中对该未知类型（unknown）成员函数指针给出类型定义并赋值，这就必须使用编译选项codice_12来编译此源文件。codice_12编译选项使得编译单元中所有的类成员函数指针均为四倍尺寸。可以用上述3个Microsoft定义的继承关系关键字把那些不是未知类型的成员函数指针显式地给出其类继承关系是单继承、多继承、虚继承，从而使该类的成员函数指针分别是单倍、二倍、三倍的尺寸。
类成员函数指针的用途.
类成员函数指针的主要用途是把数据与相关代码结合在一起。这与委托（delegate）、函子（functor）、闭包（closure）等概念很像。虽然C++对此支持的并不太好。
MFC类体系中，Windows消息传递处理机制是基于CCmdTarget类及其派生类的静态数据成员与静态成员函数GetThisMessageMap()。用户所写的类中的Windows消息处理函数（例如OnCommand）必须转换为CCmdTarget::*的成员函数指针类型AFX_PMSG，保存在该用户类的_messageEntries静态数组中。
<syntaxhighlight lang="cpp">typedef void (CCmdTarget::*AFX_PMSG)(void);</syntaxhighlight>
调用用户类中该消息处理函数时，根据该函数保存在_messageEntries中的signature（一个无符号整型表示的函数的形参类型列表与返回值类型），把类型为void (CCmdTarget::*AFX_PMSG)(void)的成员函数指针强制转为其它类型的CCmdTarget成员函数指针（例如void (AFX_MSG_CALL CWnd::*pfn_v_i_i)(int, int)，目前在union MessageMapFunctions中列出了近百种CCmdTarget成员函数指针），然后调用转换后的成员函数指针。这是基于Visual C++编译器把单继承的成员函数指针编译为只保存了函数的内存起始地址，因此可以在同一个单继承类中把一种类型的成员函数指针强制转换为另一种成员函数指针，或者把单继承派生类的成员函数指针强制转换为基类成员函数指针。这是打破了C++标准的违例办法。例如，对于CWnd::OnCommand函数，转换过程是：
<syntaxhighlight lang="cpp">BOOL (CWnd::*)(WPARAM, LPARAM lParam) => void (CWnd::*)() => void (CCmdTarget::*)()</syntaxhighlight>
例子.
<syntaxhighlight lang="cpp">
class Test; //一个未定义的类。
class Test2 
public:
class Test3
 int i;
public:
class Test4:public Test2 , public Test3 //多继承的类 
 int i;
public:
class Test5:virtual public Test4 //虚继承的类 
 int i;
public:
int main()
std::cout Â«"Test3类成员函数指针长度="Â«sizeof(void(Test3::*)()) Â«'\n';
std::cout Â«"Test4类成员函数指针长度="Â«sizeof(void(Test4::*)()) Â«'\n';
std::cout Â«"Test5类成员函数指针长度="Â«sizeof(void(Test5::*)()) Â«'\n'; 
std::cout Â«"Test类成员函数指针长度="Â«sizeof(void(Test::*)()) Â«'\n';
//以下可以打开IDE的反汇编（Disassembly）窗口观察成员函数指针的赋值与调用
Test5 a; //定义一个实例
void (Test5::* pfunc)()=&Test5::foo; //定义类成员函数指针并赋值
pfunc=&Test5::Test2::foo;
pfunc=&Test2::foo;
pfunc=&Test5::Test3::foo;
(a.*pfunc)(); //调用类成员函数指针，同时使用了虚基表（vbtbl）索引值与this指针调整值
</syntaxhighlight>
未知继承的成员函数指针例子.
使用Microsoft Visual C++编译32位程序：
<syntaxhighlight lang="cpp">
//main.cpp 不需要任何特殊的编译选项
class Test; //一个forward declaration、未定义的类
typedef void(Test::*NULLFUNCPTR)(); //未知继承的类成员函数指针的类型定义
extern Test var; //外部定义的全局对象
extern NULLFUNCPTR pfunc; //外部定义的类成员函数指针的变量
void set(); //外部定义的对类成员函数指针pfunc初始化
void Helper(Test &var, NULLFUNCPTR pf)
 (var.*pf)();
int main()
 size_t ss=sizeof(NULLFUNCPTR);
 Helper(var, pfunc );
</syntaxhighlight>
<syntaxhighlight lang="cpp">
// DefineTest.cpp 
//必须用Visual C++编译选项/vmg
class t2 
public:
class t3
public:
 printf("In t3::foo() %d\n",i);
};
class t4:public t2 , public t3 //多继承的类 
public:
class Test:virtual public t4 //虚继承的类 
public:
/* 类成员函数指针的类型定义。因为使用了/vmg编译选项，该类Test的成员函数指针类型为16字节长。
如果不使用/vmg编译选项，在本文件中有类Test的完整定义，所以编译器会把类Test的成员函数指针类型
按照虚继承的情形定义为12字节长，
这导致与main.cpp的类Test的未知继承的成员函数指针类型（16字节长）不匹配，
程序运行时出错 */
typedef void(Test::*NULLFUNCPTR)(); 
Test var; //全局对象，用于main.cpp
NULLFUNCPTR pfunc; //全局成员函数指针，用于main.cpp
void set() //初始化pfunc
 size_t ss=sizeof(NULLFUNCPTR); //长度是16；如果不用/vmg编译选项则为12
/* 赋初值。pfunc的16字节依次存入了
 pfunc=&t3::foo3; 
/* 上述语句右端是&t3::foo或者&Test::t3::foo，pfunc实际对应到了
Test::t3的虚表的第一项所保存的函数，即Test::foo()的thunk（用于在调用
Test::foo()之前，把this指针从指向Test::t3调整到指向Test实例的开始地址） */
</syntaxhighlight>