成员函数指针并非常规的指针

  • 成员指针分为成员函数指针数据成员指针 。数据成员指针 /虚函数成员指针并没有真的指向一个内存 ,仅仅是表示在当前类,那个字段的位置 。如:&X::value 表示的只是这个数据成员value 在类X 中的位置。
  • 数据成员指针 /虚函数成员指针是一种类似于偏移量 的东西,而成员函数指针真正的存储了一个地址。
  • 成员指针无法脱离类的实例对象单独使用 ,无论是非静态数据成员指针还是非静态成员函数指针。
  • 静态数据成员和静态成员函数不与类关联,不参与这个成员指针的讨论。

代码存储位置:shanchuann /Modern_CPP

成员函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
struct X
{
void f() { cout << "1"<<endl; }
};
int main()
{
void (X:: * p)() = &X::f; // p是成员函数指针
X x;
(x.*p)(); // 通过对象调用成员函数
X* px = &x;
(px->*p)(); // 通过对象指针调用成员函数
return 0;
}
//打印结果:
//1
//1

使用更为直观的传参的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
struct X
{
void f() { cout << "1" << endl; }
};
void f2(void(X::* p)(), X& x) {
(x.*p)(); // 通过对象调用成员函数
}
int main()
{
X x;
f2(&X::f, x); // 传递成员函数指针
return 0;
}

注:当你的左操作数为引用或普通实例时,访问成员指针时使用.* 运算符,而为指针时采用->* 运算符。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct X
{
void f() { cout << "1" << endl; }
};
void f2(void(X::* p)(), X* x) {
(x->*p)(); // 通过对象调用成员函数
}
int main()
{
X x;
f2(&X::f, &x); // 传递成员函数指针
return 0;
}

成员函数重载

问:如何确定绑定的是哪个成员函数呢?

答:使用static_cast 指定类型的成员函数为成员函数指针进行类型转换来消除歧义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Test_bind
{
void t(int n) {
for (; n; n--) {
cout << "t(int n)" << endl;
}
}
void t() {
cout << "t()" << endl;
}
};

int main()
{
void (Test_bind:: * p1)(int) = static_cast<void(Test_bind::*)(int)>(&Test_bind::t);
void (Test_bind:: * p2)() = static_cast<void(Test_bind::*)()>(&Test_bind::t);
Test_bind tb;
(tb.*p1)(3); // 通过对象调用成员函数
(tb.*p2)(); // 通过对象调用成员函数
return 0;
}

输出:

1
2
3
4
t(int n)
t(int n)
t(int n)
t()

同样的,其他普通函数的重载也可以通过此方式解决。

注:operator.* 不可以重载,但operator->* 可以

1
2
3
4
5
6
7
8
9
10
11
12
struct X {
void f() { cout << "1" << endl; }
template<typename Ty>
auto operator->*(Ty p) {
return (this->*p)();
}
};

int main() {
X x;
x->*& X::f; // 通过重载的`operator->*`调用成员函数 //1
}

数据成员指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct X
{
int x = 1;
};
int main()
{
int X::* n = &X::x; // p是数据成员指针
X x;
cout << x.x << endl; // 通过对象访问成员变量 //1
int& v = (x.*n);
v = 10; // 通过成员指针访问成员变量
cout << x.x << endl; //10
return 0;
}

与绑定成员函数指针类似,我们直接得到了x对象的数据成员x的引用 /

传参写法

1
2
3
4
5
6
7
8
9
10
11
12
13
struct X
{
int x = 1;
};
void f(int X::* p, X* x) {
(x->*p) = 10; // 通过成员指针访问成员变量
}
int main()
{
X x;
f(&X::x, &x); // 传递成员变量指针
cout << x.x << endl; //10
}

这些一般由我们间接使用,如C++17中的invoke

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test {
public:
uint16_t num = 0;
void f() {
cout << "f()" << endl;
}
};

int main() {
Test t;
uint16_t& i = std::invoke(&Test::num, &t); // 通过成员指针访问成员变量
i = 10;
cout << t.num << endl; // 10
std::invoke(&Test::f, &t); // 通过成员指针调用成员函数,无返回值
}

成员访问运算符

运算符名 语法 可重载 原型实例(对于class T )类定义内 原型实例(对于class T )类定义外
下标 a[b] R &T::operator[](S b);``R &T::operator[](S1 s1, ...); N /A
间接寻址 *a R &T::operator*(); R &operator*(T a);
取地址 &a R* T::operator &(); R* operator &(T a);
对象的成员 a.b N /A N /A
指针的成员 a->b R* T::operator->() N /A
指向对象的成员的指针 a.*b N /A N /A
指向指针的成员的指针 a->*b R &T::operator->*(S b); R &T::operator->*(T a,S b);
  • 下标运算符 :C++23起支持多参数下标操作
  • 成员运算符(. 和 .*) :不可重载,这是C++的语法限制
  • 类定义外重载 :只有部分运算符支持在类外重载为全局函数
  • 返回类型 R :表示运算符的返回类型,具体取决于实现

内建的下标(subscript)运算符内提供对指针或数组操作数所指对象的访问。对于数组arr 和索引iarr[i] 等价于*(arr + i)

内建的 间接寻址(indirection)运算符 提供对它的指针操作数所指向的对象或函数的访问。

内建的 取地址 (address of)运算符 创建指向它的对象或函数操作数的指针。

对象的成员 和 指向对象的成员的指针 运算符提供对它的对象操作数的教据成员或成员函数的访问。

内建的 指针的成员 和 指向指针的成员的指针运算符 提供对它的指针操作数所指向的类的数据成员或成员函数的访问。

成员指针都存什么

普通成员函数指针

普通的成员函数指针存储明确的地址

1
2
3
4
5
6
7
8
9
struct X {
void f() { cout << "func" << endl; }
};
int main() {
using Func = void(*)(X* const);
auto p = &X::f; // p是成员函数指针
auto func = (Func)(p); // 转换为普通函数指针
func(nullptr);
}

由于MSVC管制较严,所以采用G++进行编译

image.png

数据成员指针

存储的为偏移量而非明确地址

1
2
3
4
5
6
7
8
9
10
11
12
struct X {
int a, b;
double d;
};
int main() {
auto p1 = &X::a;
auto p2 = &X::b;
auto p3 = &X::d;
cout << *reinterpret_cast<int*>(&p1) << endl; // 输出成员变量a的偏移量 0
cout << *reinterpret_cast<int*>(&p2) << endl; // 输出成员变量b的偏移量 4
cout << *reinterpret_cast<int*>(&p3) << endl; // 输出成员变量d的偏移量 8
}

虚成员函数指针

同样的,虚函数成员存储的也为偏移量

1
2
3
4
5
6
7
8
struct X {
virtual void f() { cout << "X::f()" << endl; }
};
int main() {
auto ptr = &X::f;
auto func = *(int*)(&ptr); // 获取虚成员函数指针的实际地址
cout << hex << func << endl; // 输出虚成员函数的地址 //6a5e14a1
}