跳至內容

使用者: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.