2011-08-26 54 views
13

什麼是C++ 98/C++ 03標準和C++ 0x未來標準的確切規則dominance in virtual inheritance虛擬繼承中的優勢

我並不是要求特定的段落,雖然我也在問(第10節中的某處,我猜)。

我也在問清楚標準的解釋,標準的後果。

+0

對於虛擬繼承,「統治」意味着什麼? –

+1

@尼科爾:C++ 98標準的索引是指虛擬繼承和第167頁。這就是所有的標準直接說(據我所知,索引是非規範的)。解釋優勢將超過此評論,所以,只是谷歌它,或[維基百科](http://en.wikipedia.org/wiki/Dominance_%28C%2B%2B%29) - 如果你不知道關於它,無論如何你可能無法回答這個問題。歡呼聲, –

+0

@尼科爾:感謝提問,我添加了該問題的鏈接。 –

回答

25

我認爲這是你正在尋找的語言。在C++ 03 ISO規格,在§ 10.2/2,我們有以下:

以下步驟定義的一類範圍,C.首先,每個聲明爲 名稱名稱查找的結果在類中以及在其每個基類中的子對象被考慮​​。如果A是B的基類子對象,則在一個子對象B中的成員名稱f隱藏了子對象A中的成員名稱f。任何被隱藏的聲明 都不考慮。由 使用聲明引入的每個聲明均被視爲來自C的每個子對象,該子對象的類型包含using聲明指定的聲明。如果得到的一組聲明不是全部來自同一類型的子對象 ,或者該組具有非靜態成員並且包含來自不同子對象的成員,則存在不明確性並且該程序不合格。否則,該設置是查找的結果。

從更高的層面來看,這意味着當您嘗試查找名稱時,它會查找所有基類和類本身以查找該名稱的聲明。然後,您可以按類逐個學習,如果其中一個基礎對象具有該名稱的某個名稱,則會隱藏該對象的任何基類中引入的所有名稱。

這裏有一個重要的細節是這一行:

是如此隱蔽的任何聲明都 考慮消除。

重要的是,這表示,如果事情是由什麼隱藏,它被認爲是隱藏和刪除。因此,舉例來說,如果我這樣做:

      class D { 
          public: 
           void f(); 
          } 

    class B: virtual public D {  class C: virtual public D { 
    public:       public: 
     void f();       /* empty */ 
    };         }; 

         class A: public B, public C { 
         public: 
          void doSomething() { 
           f(); // <--- This line 
          } 
         }; 

在指定的行調用f()如下解決。首先,我們將B::fD::f添加到可以考慮的名稱集合中。 D::f不會隱藏任何內容,因爲D沒有基類。但是,B::f確實隱藏了D::f,因此即使D::f可以從A到達,而沒有看到B::f,但它被視爲隱藏並可從名爲f的對象集中移除。由於只剩下B::f,這就是所謂的。在ISO規格提及(§ 10.2/7)

當使用虛擬基類,一個隱藏的聲明可以沿着通過子對象 晶格不穿過遮蓋聲明的路徑到達。這不是一個含糊不清的問題。 [...]

我認爲這是因爲上述規則。

在C++ 11中(根據草案規範N3242),規則比以前明確得多,並給出了實際的算法來計算名稱的含義。這是一步一步的語言。

我們首先§ 10.2/3:

在C f顯示查找集合,稱爲S(F,C),由兩個分量集:聲明設置,一組成員 命名爲f;和子對象集,這是一組子對象,其中聲明瞭這些成員(可能包括使用聲明)。在聲明集中,使用聲明被它們指定的成員替換,並且類型聲明(包括注入類名稱)被它們指定的類型替換。 S(F,C)如下計算:

在這方面,C是指其中所述查找發生的範圍。換句話說,集合S(f, C)的意思是「當我嘗試在類範圍C中查找f?時可見的聲明是什麼?」爲了回答這個問題,該規範定義了一個算法來確定這一點。第一個步驟是如下:(§ 10.2/4)

如果C含有名稱f的聲明,該聲明集包含的F IN Ç滿足語言的要求構造中聲明每個聲明查找發生。 [...]如果生成的聲明集不爲空,則子對象集合本身包含C ,並且計算已完成。

換句話說,如果類本身有f一些所謂的在它聲明,那麼宣言中只是一套東西命名爲f在類中定義(或用using申報進口)。但是,如果我們找不到任何名爲f的東西,或者如果所有名爲f的東西都是錯誤的類型(例如,我們需要類型時的函數聲明),那麼我們繼續下一步:(§ 10.2/5 )

否則(即,C不包含f的聲明或結果聲明集爲空),S(f,C)初始爲空。如果C具有基類,則計算每個直接基類子對象f中的f的查找集合,並依次將每個這樣的查找集合S(f,B 和)合併到S(f,C)中。

換句話說,我們將查看基類,計算這些基類中的名稱可能引用的內容,然後將所有內容合併到一起。下一步將指定您合併的實際方式。這真的很棘手(它有三個部分),所以這是一擊一吹。這是原來的措辭:(§ 10。2/6)

以下步驟定義合併查找集S(f的結果,B )到中間S(F,C):

  • 如果每個的S(f,B i)的子對象成員是S(f,C)的子對象的至少一個子對象 的基類子對象,或者如果S(f,B 和)爲空,則S f,C)不變並且合併完成。相反,如果S(f,C)的每個子對象成員都是S(f,B i)的至少一個子對象成員的基類子對象,或者S(f,C)爲空,新的S(f,C)是S(f,Bi)的副本。

  • 否則,如果聲明集S的(F,B )和S(F,C)不同,合併是不明確的:新 S(F,C)是具有設定的查找無效的聲明集合和子對象集合的聯合。在隨後的 合併中,無效聲明集被認爲與其他任何聲明集都不相同。否則,新的S(f,C)是具有共享聲明集合和子對象集合的聯合的查找集合。

好,讓這一塊除了一次一個。這裏的第一條規則有兩個部分。第一部分說如果你試圖將一組空的聲明合併到整個集合中,你根本就不會做任何事情。這就說得通了。它還說,如果你試圖合併一些已經合併到目前爲止基礎類的東西,那麼你什麼都不做。這很重要,因爲這意味着如果你隱藏了一些東西,你不想通過重新合併來重新引入它。

第一條規則的第二部分說如果你正在合併的東西in從已經合併到目前爲止的所有內容中派生而來,您將您到目前爲止計算的集合替換爲您爲派生類型計算的數據。這基本上說,如果你已經合併了許多看起來沒有連接的類,然後在統一所有這些類的類中合併,那麼拋出舊數據並使用已經計算出的派生類型的數據。

現在我們來看第二條規則。這讓我花了一段時間才明白,所以我可能有這個錯誤,但我認爲這是說如果你在兩個不同的基類中進行查找並取回不同的東西,那麼這個名稱是不明確的,你應該報告某事是如果你想在這一點上試圖查看這個名字,那就錯了。

最後一條規則說如果我們不在這兩種特殊情況之一,沒有什麼是錯的,你應該把它們合併起來。

P ......那很難!讓我們來看看當我們追查上面的鑽石繼承時會發生什麼。我們要查找名稱f,從A開始。由於A沒有定義f,因此我們計算從B開始查找f和從C開始的f的值。讓我們看看發生了什麼。當計算fB中的含義時,我們看到定義了B::f,所以我們停止查找。在B仰視f值是集(B::fB}。要查找什麼f意味着C,我們期待在C,並認爲它並沒有定義f,所以我們再次遞歸從D查找值。在D中進行查找得到{D::f,D},並且當我們將所有內容合併在一起時,我們發現應用規則1的後半部分(因爲子對象集合中的每個對象都是基地D),所以最終C的值由{D::f,D}給出。

F最後,我們需要合併BC的值。這嘗試合併{D::f,D}和{B::fB}。這是它獲得樂趣的地方。假設我們按照這個順序合併。合併{,D}和空集產生{D::f,D}。當我們現在合併在{B::fB}中時,那麼因爲DB的一個基數,所以在規則一的後半部分,我們覆蓋舊的集合,並以{B::fB}結束。因此,f的查找是Bf的版本。

如果,另一方面,我們以相反的順序合併,我們先從{B::fB}並嘗試合併在{D::fD}。但由於DB的基數,我們只是忽略它而留下{B::fB}。我們已經達到了相同的結果。很酷,是吧?我很驚訝,這工作得很好!

所以你有它 - 舊的規則是真的(簡單)直接,新規則是不可能的複雜,但無論如何設法解決。

希望這會有所幫助!

+0

@Alf P. Steinbach-我剛剛用C++ 11的規則更新了這個,而且這個人很笨。希望這有助於解釋事情(?)! – templatetypedef

+0

+1用於涉水。我想我現在理解了C++ 98的一部分。好。令人驚訝的簡單。然而,當我開始閱讀C++ 11部分時,我的目光掠過......乾杯, –

+2

+1只是爲了努力必須進入這個答案o_O – ildjarn