2015-11-06 72 views
1

爲了更好地理解我的問題,我指的是「C++編程語言第4版」第27章2.1節中討論的主題。爲什麼原始數組的多態類型是危險的

作者正在討論多態類型和內置數組的危險。 他給出了下面的例子:

void maul(Shape∗ p, int n) // Danger! 
{ 
    for (int i=0; i!=n; ++i) 
     p[i].draw(); //looks innocent; it is not 
} 

void user() 
{ 
    Circle image[10]; // an image is composed of 10 Circles 
    // ... 
    maul(image,10); // ‘‘maul’’ 10 Circles 
    // ... 
} 

我們被告知外形尺寸是4和Circle繼承形狀,並添加一個額外的2名成員,中心和半徑增加了文字大小,因此,一個抽象的大小, sizeof(Circle)>sizeof(Shape)。 現在作者解釋了該給定例如以下觀點:

user() view: image[0] image[1] image[2] image[3]<br/> 
maul() view: p[0] p[1] p[2] p[3] 

p[1].draw()呼叫(強調P []中,P [0],它會調用正確的函數)將失敗,因爲沒有虛擬函數指針在預期的地方。

現在我知道虛擬函數表是如何工作的,但我不明白類型或其佈局的大小如何影響虛擬函數調用?當編譯器看到一個虛函數調用不與它類似的東西取代它:

p[1]._vfptr->draw_impl(); 

假設我是對的,怎麼會是派生對象的大小/它的佈局打破了呼籲它。

+3

當你甚至沒有「Circle」對象的正確地址時,你會如何做一個虛函數調用? – Brian

+0

你明白'image + 1!= p + 1'嗎? – Jarod42

+0

謝謝你解釋,我已經閱讀過有關切片的知識,它對我來說很清楚。 –

回答

0

i是0,這個工程:

p[i].draw(); // Shape* p 

p[0]相同*(p+0)這只是*p,並通過調用基類指針的函數是什麼虛函數被設計爲支持。

但是i是1時呢?現在你有p[1]這是*(p+1)。什麼是p+1,好吧,它是p之後的「下一個」對象的地址。但是,數組索引是C中的一個概念,其中沒有虛擬表或派生類,因此索引是通過添加編譯時間sizeof(*p)sizeof(Shape)完成的。你喜歡的東西最終會:

((Shape*)((char*)p + sizeof(Shape)))->draw(); 

也就是說,通過sizeof(Shape)字節遞增p(的Circle在數組的開始),然後取消對它的引用調用draw()。但是這個索引是錯誤的,因爲它應該是sizeof(Circle)字節。而給我們數組類型的C並沒有給我們任何工具來直接處理這個問題,因爲它沒有預見到C++。

需要注意的是C++ 11 std::array<>不會幫助,不是進行上述編譯時錯誤(我想是不是運行未定義行爲你用C數組得到更好的)等。和好老std::vector<>是一樣的。您可以存儲指針數組,在這種情況下,它們通常應該是智能指針,如std::shared_ptr。這是有效的,因爲指向任何類型的指針(除了指向成員函數的指針!)都是相同的大小,因此通過它們的數組索引可以正常工作。