跳转到内容

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 的声明。全局作用域中的 fGrandparent::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::fParent::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 段):

当使用虚继承时,可以沿着子对象晶格中一条不经过被隐藏声明的路径访问被隐藏声明。这并不构成歧义。 [1]

即便 Child 自身虚继承 Grandparent ,也不会在名字查找时引起歧义。然而,如果 ChildGrandparent 虚继承(即 struct Child : public Mother, Father, Grandparent ),那么在两个由 Grandparent 形成的子对象中所声明的 f 成员之间将再次出现歧义。

外部链接

[编辑]

参考文献

[编辑]
  1. ^ 1.0 1.1 N3797 Working Draft, Standard for Programming Language C++. Dated 2013-10-13.