指向成员函数的指针(Pointer to Member Function)。
1. 基本语法示例
cpp
class Data1 {
public:
int a;
int getA() const { return a; }
void setA(int val) { a = val; }
};
// 声明并初始化一个指向成员函数的指针
int (Data1::* fp)() const = &Data1::getA;
void (Data1::* fp2)(int) = &Data1::setA;
2. 类型声明的语法拆解
以 int (Data1::* fp)() const 为例:
int:成员函数的返回值类型。(Data1::* fp):Data1::*表示"指向Data1的成员",fp是指针变量名。括号是必需的,因为()的优先级高于*。():函数参数列表(这里是空参数)。const:成员函数的常量限定符(如果有的话必须完全匹配)。
对比普通函数指针:
- 普通函数指针:
int (*p)(double)→ 指向一个参数为double、返回int的函数。 - 成员函数指针:
int (Data1::* p)(double)→ 指向Data1的一个参数为double、返回int的成员函数。
3. 初始化(赋值)
使用 &类名::成员函数名 获取成员函数的指针:
cpp
int (Data1::* fp)() = &Data1::getA;
void (Data1::* fp2)(int) = &Data1::setA;
注意:
- 不能写成
&getA(缺少类作用域)。 - 取成员函数的地址时,
&可以省略(C++ 允许隐式转换),但建议显式写出。 - 静态成员函数的指针是普通函数指针,不是成员函数指针。
4. 如何调用(解引用)
必须结合具体对象或对象指针,使用 .* 或 ->* 运算符:
cpp
Data1 obj;
obj.a = 100;
int (Data1::* fp)() = &Data1::getA;
int value = (obj.*fp)(); // 等价于 obj.getA()
Data1* p = &obj;
value = (p->*fp)(); // 等价于 p->getA()
关键点:
- 调用时必须加括号
(obj.*fp)(),因为.*优先级低于函数调用()。
若写成obj.*fp()会被解析为obj.*(fp()),错误。 - 可以保存返回值,传递参数同理。
5. 成员函数指针的类型必须精确匹配
包括:
- 返回值类型
- 参数类型(个数、顺序、const/引用等)
- const / volatile / ref-qualifier (C++11 的
&或&&)
cpp
class Data1 {
public:
int getA() const; // const 成员
int getA(); // 非 const 成员,重载
};
int (Data1::* fp1)() const = &Data1::getA; // OK,指向 const 版本
int (Data1::* fp2)() = &Data1::getA; // OK,指向非 const 版本
// 错误:类型不匹配
// int (Data1::* fp3)() = &Data1::getA; // 歧义?实际上编译器会选择非 const 版本,前提是只有一个匹配
如果存在重载,需要显式类型转换或通过上下文消除歧义,通常用 static_cast:
cpp
int (Data1::* fp)() const = static_cast<int (Data1::*)() const>(&Data1::getA);
6. 指向虚函数的成员指针
指向虚函数的指针同样遵循多态行为:
cpp
class Base {
public:
virtual void print() { cout << "Base\n"; }
};
class Derived : public Base {
public:
void print() override { cout << "Derived\n"; }
};
void (Base::* pf)() = &Base::print;
Derived d;
Base* p = &d;
(p->*pf)(); // 输出 "Derived",因为虚函数通过指针调用会动态分派
7. 常用类型别名简化
cpp
using GetAFunc = int (Data1::*)() const;
using SetAFunc = void (Data1::*)(int);
GetAFunc fp = &Data1::getA;
SetAFunc fp2 = &Data1::setA;
8. 与数据成员指针的异同
| 特性 | 数据成员指针 int Data1::* |
成员函数指针 int (Data1::*)() |
|---|---|---|
| 声明语法 | int Data1::* p; |
int (Data1::* p)(); |
| 初始化 | &Data1::a |
&Data1::func |
| 需要对象/指针才能使用 | obj.*p 或 ptr->*p |
(obj.*p)() 或 (ptr->*p)() |
| 调用/访问时加括号 | 不需要 ()(直接访问成员) |
必须加 (),且括号位置特殊 |
| 支持重载 | 不涉及 | 需要类型转换消除歧义 |
| 支持虚函数 | 不涉及 | 支持,调用时动态分派 |
| 支持 const/volatile | 成员本身的 const(指针声明无关) | 必须与函数限定符完全匹配 |
| 底层实现 | 偏移量(字节) | 通常为"thunk"或函数地址 + 调整量 |
9. 完整示例
cpp
#include <iostream>
using namespace std;
class Data1 {
public:
int a;
Data1(int x) : a(x) {}
int getA() const { return a; }
void setA(int x) { a = x; }
void print() const { cout << "a = " << a << endl; }
};
int main() {
// 指向成员函数的指针
int (Data1::* getter)() const = &Data1::getA;
void (Data1::* setter)(int) = &Data1::setA;
void (Data1::* printer)() const = &Data1::print;
Data1 obj(10);
Data1* p = &obj;
// 调用
cout << (obj.*getter)() << endl; // 10
(p->*setter)(20);
(obj.*printer)(); // a = 20
return 0;
}
10. 注意事项与最佳实践
- 优先级和括号 :调用时
(obj.*ptr)()不能省略外层的括号。 - 类型别名 :推荐用
using简化复杂的成员函数指针类型。 - 指向静态成员函数:使用普通函数指针,而不是成员函数指针。
- 与
std::function结合 :可以使用std::function<int(const Data1&)>包装成员函数,配合std::bind或 lambda 更现代。
cpp
#include <functional>
auto getter = std::mem_fn(&Data1::getA);
int val = getter(obj); // 更简洁
思考:
std::mem_fn的原理与用法- 成员指针与模板元编程的结合
- 成员指针在库设计中的实际案例(如信号槽、属性系统)