为什么成员函数指针的类型基于函数实际声明的类

Why does a member function pointer have a type based on the class where the function actually declared?

本文关键字:函数 声明 于函数 指针 成员 类型 为什么      更新时间:2023-10-16

看看这个片段:

struct A {
void fn();
};
struct B: A {
};
void f() {
auto x = &B::fn;
}

在这里,x得到了一种类型的void (A::*)(),尽管我已经编写了&B::fn

如果我将fn添加到B中,那么x的类型将是void (B::*)()

所以,无论B是否具有fn&B::fn的类型都会发生变化。

这种行为背后的理由是什么?我觉得很奇怪。

为什么这很重要?假设这样:程序员X创建AB,就像我的例子一样。程序员Y使用&B::fn,并将其类型的类部分用于某些东西(比如模板的参数,等等)。然后程序员X意识到他需要fn中的一些额外功能,所以他重写了它。现在,由于&B::fn的类型发生了变化,程序员Y的代码可能会被破坏。

这是CWG第203期和EWG第89期的主题。最初,基本原理是允许尽可能多的代码有效:

04/00会议笔记:

目前处理的理由是允许尽可能广泛地使用给定的成员表达地址。由于指向基成员的指针可以隐式转换为指向派生成员的指针,因此将表达式的类型设置为指向基成员指针可以使结果初始化或分配给指向基成员或指向派生成员指针。接受这一提议将只允许后者使用。

后来,在它引起的问题变得更加明显后,修复为时已晚:

附加说明,2015年4月:

EWG已经确定,这种更改的效用会被它破坏代码的事实所压倒。参见EWG第89期。

我认为主要思想来自于通常的"B是A"类继承定义。您可以将其改写为成员函数,如"A的函数是B的函数",但翻转A和B位置的语句仅对某些项正确,即"只有B的某些函数是A的函数"。因此B::fn属于B的这一类函数,即A的函数。通过将函数fn写在B类中,我们同时将B::fn从这一类中移到B的这类函数中,即非A的函数中。

这允许检查类是否覆盖基类的某些方法:

const bool fn_is_overriden{::std::is_same<decltype(&A::fn), decltype(&B::fn)>::value};

在大多数情况下,成员函数就像一个带有隐式对象参数的自由函数。所以这个代码:

struct A {
void fn();
};

与非常相似

struct A {};
void A_fn(A* this_);

例如,成员函数和自由函数之间的重载解析就是这样定义的,这样所有函数都可以在相同的基础上进行排序。

当你继承一个成员(变量或函数)时,你会按原样继承该函数。继承不会定义新的函数或变量。它所做的只是在派生类中创建引用基类现有成员的名称。

您仍然有A_fn(A* this),只是它可以从B的作用域访问。继承的访问规则仅适用于继承的名称,不会更改成员的任何属性。