为什么C++标准允许声明指向不存在类成员类型的指针?
核心原因:成员指针的合法性仅和类型定义有关,和类是否存在对应成员无关
C++的成员指针类型的定义仅依赖两个要素:
- 所属的类类型(此处为
Foo,声明时已为完整类型) - 成员自身的类型:对于成员数据指针是成员的类型,对于成员函数指针是函数的签名、cv/ref限定符
你示例中的std::string (Foo::*)(char)const和std::string Foo::*本身就是完全合法的类型,只要类型本身合法,声明对应类型的变量就是被允许的,和Foo是否真的存在对应签名的成员函数、对应类型的成员数据没有关系。
这一规则的设计考量
和普通指针的语义保持一致
你可以把成员指针理解为一种特殊的指针,它和普通指针遵循相同的基础规则:你可以声明一个空的普通指针int* p = nullptr;,不需要它当前必须指向一个存在的int对象,只有对指针解引用、或者给它绑定具体地址的时候才会检查目标的合法性。
你示例中的std::string Foo::* pMemDt = nullptr;本质上就是声明了一个空的成员数据指针,和你声明std::string* p = nullptr;没有本质区别,自然是合法的。你提到的基类指针静态检查属于类型系统的子类型关系校验,是类型本身的属性;而成员指针是否指向真实存在的成员属于值的属性,不属于类型校验的范畴,C++不会在类型声明阶段对值的有效性做检查,就像不会在你声明int* p的时候检查p是不是空、是不是指向野地址一样。
满足泛型编程、元编程的需求
很多泛型场景下,我们需要先声明对应类的成员指针类型,再根据逻辑动态绑定成员:
比如写通用的类成员遍历模板、实现依赖注入的回调绑定、做类型擦除的中间存储时,只需要保证成员指针的类型符合要求即可,不需要在声明阶段就确认类一定存在对应成员——如果后续绑定不到合法成员,在赋值或者调用阶段报错完全符合C++"不为不需要的功能付出开销"的设计哲学。
兼容跨单元开发的场景
如果你的项目中Foo的实现分散在多个编译单元,或者你在开发库的时候预留了成员指针的扩展接口,允许后续给Foo新增成员再给指针赋值,声明阶段就检查成员存在性会直接阻断这类扩展需求。
什么时候才会检查成员存在性
只有两种场景下编译器会检查Foo是否存在对应成员:
- 你给成员指针赋值具体成员地址的时候,比如写
&Foo::不存在的成员,编译器会直接报错 - 你对成员指针解引用调用/取值的时候,编译器会做对应检查
内容的提问来源于stack exchange,提问作者Itachi Uchiwa

