User:Redstone1024/优先性规则(C++)
在 C++ 编程语言中,优先性规则是指在涉及继承的情况下,对类成员的无限定名字查找的一种特殊规则。当编译器计算一个名字可能对应的声明集合时,那些来自更远基类且不如较近基类中的声明“优先”的声明,将在名字查找中被隐藏。在其他语言或语境中,同样的原则可能被称为“名字屏蔽”或“变量遮蔽”。
计算名字查找的算法在 C++11 标准的 10.2 节 [class.member.lookup] 中描述[1]。标准的描述没有使用“优先”这个词,而是倾向于用“查找集合”和“隐藏”来描述事物。但是索引中包含一个引用到第 10.2 节的“优先、虚基类”条目。
非菱形继承示例
[编辑]void f(double, double); // 全局作用域
struct Grandparent {
void f(int);
void f(double, double);
};
struct Parent : public Grandparent {
void f(int); // 隐藏 Grandparent::f 的所有重载
};
struct Child : public Parent {
void g() { f(2.14, 3.17); } // 解析为 Parent::f
};
在上述例子中, Child::g
包含对名字 f
的引用。然而,然而,整个程序中存在四个 f
的声明。为了确定究竟指的是哪一个 f
,编译器会计算一个重载集合,其中包含在调用点未被隐藏的所有 f
的声明。全局作用域中的 f
被 Grandparent::f
隐藏,而 Grandparent::f
又被 Parent::f
隐藏。因此,重载决议只考虑 Parent::f
这一声明 — 最终的结果是非良构的,因为调用提供了两个参数,而 Parent::f
只期望一个参数。
对于新 C++ 程序员而言,通常令他们感到意外的是,基类中 Parent::f
的声明会优先并隐藏所有来自更远基类的同名声明,而不论各自的函数签名如何;也就是说,尽管这两个成员函数的参数列表截然不同, Parent::f(int)
的声明依然优先并隐藏了 Grandparent::f(double, double)
的声明。
同样需要注意的是,在 C++ 中,名字查找先于重载决议。如果 Parent::f
拥有多个重载(例如 f(int)
和 f(double, double)
),那么编译器会在重载决议阶段从它们中进行选择;但在名字查找阶段,我们仅仅需要在 Grandparent::f
、 Parent::f
和 ::f
这三个作用域中做出选择。事实上, 虽然 Grandparent::f(double, double)
作为重载候选比 f(int)
的重载更好,但这一点并不会被编译器在名字查找过程中考虑。
菱形继承示例
[编辑]struct Grandparent {
void f(int);
void f(double, double);
};
struct Mother : public Grandparent {
void f(int); // 隐藏 Mother::Grandparent::f 的所有重载
};
struct Father : public Grandparent { };
struct Child : public Mother, Father { // Mother::Grandparent 与 Father::Grandparent 不是同一个子对象
void g() { f(2.14, 3.17); } // Mother::f 与 Father::Grandparent::f 之间存在歧义
};
在上述示例中,编译器为 f
构造了一个重载集合,其中既包含了 Mother::f
也包含了 Father::Grandparent::f
。所以编译器会生成一个诊断信息,表明此程序因为名字 f
歧义而非良构。
虚继承示例
[编辑]struct Grandparent {
void f(int);
void f(double, double);
};
struct Mother : public virtual Grandparent {
void f(int); // 隐藏 Mother::Grandparent::f 的所有重载
};
struct Father : public virtual Grandparent { };
struct Child : public Mother, Father { // Mother::Grandparent 与 Father::Grandparent 是同一个子对象
void g() { f(2.14, 3.17); } // 解析为 Mother::f
};
在这个最后的例子中,名名字 f
再次毫无歧义地指向 Mother::f
,因为 Mother::f
隐藏了在其 Grandparent
对象中声明的 f
。标准中提到了这一令人惊讶的情况(§10.2 第 10 段):
即便 Child
自身虚继承 Grandparent
,也不会在名字查找时引起歧义。然而,如果 Child
从 Grandparent
非虚继承(即 struct Child : public Mother, Father, Grandparent
),那么在两个由 Grandparent
形成的子对象中所声明的 f
成员之间将再次出现歧义。
外部链接
[编辑]参考文献
[编辑]- ^ 1.0 1.1 N3797 Working Draft, Standard for Programming Language C++. Dated 2013-10-13.