簡體   English   中英

具有多重繼承的協變返回類型。 該代碼如何工作?

[英]covariant return types with multiple inheritance. how does this code work?

誰能告訴我在以下代碼中返回類型協方差如何工作?

class X
{
public:
    int x;
};

class Y: public OtherClass, public X
{
};


static Y inst;

class A {
public:
    virtual X* out() = 0;
};

class B : public A
{
    public:
    virtual Y* out() 
    {
         return &inst;
    }
};



void main()
{
    B b; 
    A* a = &b;
    //x and y have different addresses. how and when is this conversion done??
    Y* y = b.out();
    X* x = a->out();
}

編輯:對不起,我一定還不夠清楚。 正如我期望的那樣,x和y指向不同的地址,因為涉及多個繼承,因此X和Y對象不在同一地址上。 我的問題是,此轉換何時完成? out()函數無法執行此操作,因為它始終從其角度返回指向Y的指針。 out()的調用者無法執行此操作,因為它看到X *的具體類型可能是X或Y。什么時候進行強制轉換?

那演員什么時候完成?

好吧,這是在B::out返回之后和A::out調用結束之前完成的。 真的沒有什么。


多態調用返回的指針必須為X*類型。 這意味着它必須指向類型X的對象。 由於您使用的是多重繼承,因此您的Y對象中可以包含多個子對象。 在您的情況下,它們可以具有OtherClass子對象和X子對象。 顯然,這些子對象沒有存儲在同一地址中。 當您要求指向X*的指針時,您將得到一個指向X子對象的指針,而當您要求指向Y*的指針時,您將得到一個指向整個Y對象的指針。

class OtherClass { int a; };

// the other classes

int main()
{
    B b; 
    A* a = &b;
    //x and y have different addresses. how and when is this conversion done??
    Y* y = b.out();
    X* x = a->out();
    OtherClass* o = y;
    std::cout << "x: " << x << std::endl;
    std::cout << "y: " << y << std::endl;
    std::cout << "o: " << o << std::endl;
}

您可以看到運行此代碼會X*Y*指針產生不同的地址,並為Y*OtherClass*指針產生相同的地址,因為該對象是在X子對象之前與OtherClass子對象一起布置的。

x:0x804a15c
y:0x804a158
o:0x804a158

通過“它是如何工作的”,我想您是在詢問生成的代碼是什么樣子。 (當然,在典型的實現中。我們都知道生成的代碼在不同的實現之間可能有所不同。)我知道兩種可能的實現:

編譯器總是生成代碼以返回指向基類的指針。 即在B::out ,編譯器將在返回之前將Y*轉換為X* 在調用站點,如果調用是通過具有靜態類型B的左值進行的,則編譯器將生成代碼以將返回值重新轉換為Y*

另外(我認為這更常見,但是我不確定),編譯器會生成thunk,因此當您調用a->out ,被調用的虛擬函數不是直接B::out ,而是一個小函數包裝器,將B::out返回的Y*轉換為X*

g ++和VC ++似乎都在使用thunk(從速覽)。

注意:第一個答案僅對單繼承正確,在(edit2)下的多繼承正確。

x和y具有不同的地址是正常的,因為它是兩個不同的指針。 它們確實具有相同的值,這就是它們指向的變量的地址。

編輯:您可以使用此main來檢查我的意思,第一行將打印xy的值(即它們指向的地址),該值始終應該相同,因為實際上將調用相同的方法,因為out是虛擬的。 第二個將打印自己的地址,這當然是不同的,因為它們是不同的(指針)變量。

#include <iostream>
int main()
{
    B b;
    A* a = &b;
    //x and y have different addresses. how and when is this conversion done??
    Y* y = b.out();
    X* x = a->out();
    std::cout << y << std::endl << x << std::endl;
    std::cout << &y << std::endl << &x << std::endl;
    return 0;
}

edit2:好的,這是錯誤的,我很抱歉。 當多重繼承開始起作用時,從Y*X*的隱式轉換(因此在分配x時,不是out的返回)將更改指針地址。

發生這種情況是因為在實現時, Y的布局包含2個其他類實現,這是它從OtherClass繼承的部分以及從X繼承的部分。 當隱式轉換為X* (允許)時,地址當然必須更改為指向YX部分,因為X不知道OtherClass

由於Y*可轉換為X*因此代碼可以正常工作。 當您使用A*調用out() ,則B::out()返回值僅解析為X*

由於從Y*隱式轉換為X*您不會注意到return類型的更改或問題。 只需嘗試以下兩種情況,您的代碼就會停止工作:

  1. X*更改為int*
  2. XY的繼承設為private / protected

取決於OtherClass的外觀,指針x和y所持有的值實際上在數值上可能有所不同,盡管它們指向的是同一對象-導致這種情況(對於許多人來說是令人驚訝的)的原因是多重繼承。 要了解可能的原因,您必須考慮類Y的對象的內存布局(另請參見VTable上的Wikipedia條目 )。 讓我們假設類OtherClass看起來像這樣:

class OtherClass
{
    public:
    virtual ~OtherClass() {}
};

然后,取決於您的系統和編譯器,類Y的實例可能如下所示:
0x0000 ... 4個字節-int x(從X類繼承)
0x0004 ... 4個字節-指向類Y的vtable的指針(用於OtherClass基)

在代碼的以下行中,發生了從指向Y的指針到指向X的指針的轉換:

X* x = a->out();

現在,存儲在x中的結果地址指向存儲在y中的地址之前的4個字節。 因為編譯器知道內存布局,所以它也知道在協變量類型之間進行轉換時要使用的偏移量,並將適當的加/減值放入已編譯的文件中。 但是請注意,即使指針在數字上有所不同,在進行比較時它們也將轉換為通用類型,並且這種比較將返回1。

暫無
暫無

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

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