请看这个片段:
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创建A
和B
,就像在我的例子中一样。程序员Y使用&B::fn
,并使用其类型的类部分(例如模板的参数,无论如何)。然后程序员X意识到他需要fn
中的一些额外功能,所以他会覆盖它。现在,程序员Y的代码可能会被破坏,&B::fn
的类型已经改变。
答案 0 :(得分:3)
这是CWG issue 203和EWG issue 89的主题。最初,理由是允许尽可能多的代码有效:
04/00会议的说明:
目前治疗的基本原理是允许尽可能广泛地使用给定的成员地址表达。由于指向基本成员的指针可以隐式转换为指向派生成员的指针,因此使表达式的类型成为指向基本成员的指针允许结果初始化或分配给指针 - to-base-member或指向派生的成员。接受此提议只允许后者使用。
后来,在它引起的问题变得更加明显之后,现在修复已经太晚了:
附加说明,2015年4月:
EWG已经确定这种变化的效用被它破坏代码的事实所抵消。见EWG第89期。
答案 1 :(得分:1)
我认为主要想法来自于通常的情况" B是A"类继承定义。您可以将其改为成员函数,例如" A的函数是B"的功能,但是翻转的A和B位置的语句仅对某些项目是正确的,即"只有B的某些函数是A"的功能。因此B::fn
适合作为A的函数的B类函数。通过在类B中编写函数fn
,我们同时将B::fn
移出此类别的函数类别B的不是A的功能。
这允许我们检查类是否覆盖了某个基类方法:
const bool fn_is_overriden{::std::is_same<decltype(&A::fn), decltype(&B::fn)>::value};
答案 2 :(得分:0)
在大多数情况下,成员函数就像具有隐式对象参数的自由函数。所以这段代码:
struct A {
void fn();
};
非常类似于:
struct A {};
void A_fn(A* this_);
例如,成员函数和自由函数之间的重载解析以这种方式定义,因此所有函数都可以在同一个基础上排名。
当您继承成员(变量或函数)时,您将继承该函数。继承不定义新函数或变量。它所做的只是在派生类中创建引用基类的现有成员的名称。
你还有A_fn(A* this)
,只是可以从B的范围访问它。继承的访问规则仅适用于继承的名称,它们不会更改成员的任何属性。