簡體   English   中英

虛擬繼承中相同的最派生類=父類之間的相同偏移量?

[英]Same most-derived class in virtual inheritance = same offset between parent class?

對於某個類F ,它的指針(通過new F()創建)可以向上轉換為基類的指針,例如B*C*D*E*

在此輸入圖像描述

是否保證對於某個編譯器(某個配置和某個.exe程序),一對任何提到的類的上轉地址(以字節為單位)的差異(例如,在B*C*D*選擇2)和new F() )的每個實例的E* )是一個常數嗎?

例如,這個MCVE打印8對我來說都是10次: -

#include <iostream>
class B{ public: virtual ~B(){} };
class D: virtual public B{};
class C: virtual public B{};
class E: virtual public D{};
class F: virtual public E, virtual public C{};
int main(){
    for(int n=0;n<10;n++){
        F* f = new F();
        C* c=f;
        E* e=f;
        int offset=
            (reinterpret_cast<uintptr_t>(c))
            -
            (reinterpret_cast<uintptr_t>(e));
        std::cout<<offset<<std::endl;
    }
}

我相信答案是肯定的,因為我可以static_cast它。
(我在很長一段時間內無意識地依賴這個假設。)

不同的編譯器可能會打印不同的偏移量,但我只關心某個(相同的) 程序和某個(相同的) 真正的底層類new F() )的情況。

我希望價值在每種情況下始終保持不變。 如果是這樣,我不必修復我的程序。

如果回答引用一些C ++規范,我會很高興。

編輯:修復我的代碼中的不一致(感謝curiousguy的評論)

[前言:術語:

為簡單起見,我們大致遵循Itanium C ++ ABI並放寬/概括術語:

一個適當的基BD是一個真正的基類,從不同的D 不正確的基礎是適當的基礎或D本身。

D類的適當子對象是成員或適當的基礎; 不正確的主題是一個適當的子對象或D本身。

- 前言]

[前言:錯誤的假設:

您似乎認為表達類型轉換的印象是static_cast以某種方式保證不會生成復雜的代碼。 情況並非如此, static_cast可以調用直接初始化的任何內容,包括調用構造函數: static_cast<String>("")

- 前言]

沒有合理的理由要求實現不將每個基類子對象放在D類型的完整(或大多數派生,實際上具有相同布局)對象的固定已知偏移量中; 所以問題是: 有沒有什么能阻止不正當的實施呢?

實現具有不同的布局需要什么,並且這樣的實現是否符合要求? 我們需要列出類層次結構中支持的指針移動(隱式轉換或強制轉換)。

[注繼承的(非靜態)數據成員的訪問被通過的(隱式)定義的轉化this隨后的非繼承的數據成員的訪問,因此是一種遺傳性非靜態函數的調用。]

對於:

  • XD的(不正確的)基礎子對象
  • Y的(正確的)基子X (並不重要,Y是適當的實際)
  • ZD另一個(適當的)基礎

ASCII藝術摘要(樹可能會折疊):

Y
|
X     Z
 \   /
   D

這些基數必須是明確的:基礎子對象Y必須是X唯一的那種類型。 (但是D的間接基數Y不必明確: D::Y可能不指定單個基數,只有D::X::Y必須是明確的。)

必須支持三種“簡單”層次結構運動:

  • (上) X*Y*轉換(可以隱式完成)
  • (向下NV)對於X的非虛擬基礎Y: Y*X*向下轉換可以由static_cast執行
  • (向下P)對於(可能是虛擬的)X的多態基數Y: Y*X*向下轉換可以通過dynamic_cast執行

其他更復雜的運動是Y*Z*dynamic_cast ; 這是兩個動作:向下投射然后向上投射,通過最衍生的對象D ,它不必是靜態已知的類型。 (明確執行這兩個步驟的代碼必須能夠命名為D

通常,這些操作是在至少部分構造的對象上執行的。

(C ++標准尚未明確決定是否在指向未構造對象的指針上支持轉換為指向非虛擬基礎的指針。任何涉及虛擬基礎的內容都無法在未構造的對象上完成。)

因此在實踐中,(不正確的)子對象X必須攜帶足夠的信息來定位其虛擬基礎,通常是通過明確地將其地址放在隱藏成員中,或者通過將它們的偏移存儲在vtable中。

[這意味着在構建具有虛擬基礎的適當基類期間,vtable通常不能與完整對象的vtable相同。 這與基礎子對象(無論是否為虛擬對象)的構造不同,只有非虛擬基礎。

如果通過vtable定位虛擬基礎,則意味着沒有更多可能的類布局,那么存在不同的vtable。 派生類最多的構造函數將無法隨機化布局(除非vtables在現場發布到描述所述布局)。

如果通過隱藏的數據成員定位虛擬基礎,則看起來存在不正確實現的更大靈活性。 事實上,必須支持來自多態虛擬基礎的向下轉換這一事實:多態基礎只知道其動態類型(通過所有現有實現中的vptr)。 派生類可以存儲其基類的地址(或偏移)(某些,任何)的數組, 但是基礎不能通過構造存儲關於其每個派生類的信息 ,因為必須在知道哪些類將被定義之前定義它的布局。從中得出(很容易看出sizeof(T)不能,即使在最不正確的實現中,也沒有在T中使用的類定義的單調函數,並且使用T )。

但是,通過以下任一方法,不正當的實現仍然可以支持多種布局:

(多的vtables)

如果vtables是在現場生成的,或者如果許多vtable是先驗地創建以允許具有虛擬基礎的類的不同布局,則多態基礎可以訪問足夠的信息來執行任何向下轉換。

[注意,uniques類型和vtable之間一直沒有一對一的映射,所以typeid的相等測試,即使是相同類型的表達式,也不能成為vptr之間的地址比較。 通常,類型相等是通過比較typeinfo指針來實現的。

[請注意,如果您使用的是“DLL”和動態鏈接器,各種“等效”(鏈接前符號定義相同)類型信息表(vtable和typeinfo結構)可能存在於不同的地址,但這些非融合鏈接斷開無論如何,ODR也不會被融合。]

(BLIP)

每個多態潛在的基類 (可以用作基類,因此最終或本地類可以免除)將至少有一個額外的隱藏成員:指向最派生類中的成員的指針(或者相對偏移) :( 基表) ,一個隱藏表非靜態成員。

(基表)將列出(一些,任何)基類地址(或偏移量),基本定位符(反常實現想要重新排序的那些基礎)。

BLIP (base- locators -info-ptr):一個指向類型的類型結構的指針,它包含基本定位器布局的描述,因為布局取決於在編譯時未知的派生類最多的類型時間; 請注意,BLIP可以存儲在vtable中,因為它依賴於類型,而不依賴於實例。

向下轉換將找到(基表),它是不透明的,不可能由只知道基類的代碼解釋,然后使用BLIP對其進行解碼,就像typeinfo數據包含在基類中導航的代碼一樣實現向下或向上動態dynamic_cast

這看起來格外復雜,難以做到,而且出於什么目的?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM