2013-02-18 49 views
0

我想了解C++代碼中的多態行爲。看到下面的程序的輸出,我感到驚訝。Visual Studio 2008:C++的多態行爲

下面的代碼,我在下面的編程語句期待一些編譯錯誤/崩潰的輸出。但是這個程序的輸出讓我很吃驚。

p = (Class1*) &object3; 
p->f(); 
p->g(); 

我不明白爲什麼。我正在使用Visual Studio 2008.

代碼片段。

#include "stdafx.h" 
#include <iostream> 

using namespace std; 

class Class1 
{ 
    public: 
     virtual void f() 
     { 
    cout << "Function f() in Class1\n"; 
     } 

    void g() 
    { 
    cout << "Function g() in Class1\n"; 
    } 
}; 


class Class2 
{ 
    public: 
    virtual void f() 
    { 
    cout << "Function f() in Class2\n"; 
    } 

    void g() 
    { 
    cout << "Function g() in Class2\n"; 
    } 
}; 


class Class3 
{ 
    public: 

    virtual void h() 
    { 
    cout << "Function h() in Class3\n"; 
    } 
    }; 


    int _tmain(int argc, _TCHAR* argv[]) 
    { 
    Class1 object1, *p; 
    Class2 object2; 
    Class3 object3; 

    p = &object1; 

    p->f(); 
    p->g(); 

    p = (Class1*) &object2; 

    p->f(); 
    p->g(); 

    p = (Class1*) &object3; 

    p->f(); 
    p->g(); 

    //p->h();  Compilation error 

    return 0; 

    } 

O/P:

功能的Class1的Class1中在等級2中f()的

函數G()

函數f()

函數G()在Class3中

的Class1

函數H()

Class 1中的函數g()

+1

投射到不相關的類型是未定義的行爲,所以任何事情都可能發生。告訴我打招呼的比薩餅送貨員。 – fredoverflow 2013-02-18 17:19:11

+0

這個程序沒有多態行爲,它有未定義的行爲(即任何事情都可能發生)。 – 2013-02-18 17:19:41

回答

3

你的代碼有未定義行爲

當程序有未定義的行爲時,一切都可能發生。你可能得到一個崩潰,但這不是必要的。從第1.3.24:

[...]允許不確定的行爲從與不可預測的結果完全無視的情況下,在環境中的一個記錄的方式特性翻譯或程序執行期間行爲(具有或不具有範圍發佈診斷消息),終止翻譯或執行(發佈診斷消息)。 [...]


爲什麼你有未定義行爲?

您到Class1類型的對象執行的指針的殘酷C樣式轉換(這會採用reinterpret_cast<>)至Class3類型的對象的指針。這是允許的,但行爲是不確定的。

從段落5.2。在C++ 11標準的10/7約reinterpret_cast<>

一個目的指針可以顯式轉換爲另一種類型 0.70 [..]

這使得一個對象的指針明確的演員合法。然而(同一段落):

[...]當類型的prvalue V「指針T1」被轉換成類型 「指向cv T2」,其結果是的static_cast(的static_cast(v)的)如果T1和T2都是標準佈局類型,那麼T2的對齊要求(3.9)和 不會比T1的要求更嚴格,或者 兩種類型都是無效的。 [...]

你的類Class1Class3標準佈局類型。每段9/7,事實上:

標準佈局類是一類:

- 具有式非標準佈局類的沒有非靜態數據成員(或這些陣列類型)或參考,

- 具有沒有虛函數(10.3),並且沒有虛基類(10.1),

[...]

因此,5.2.10/7的第二部分適用:

[...]轉換類型的prvalue「指針T1」的類型「指針T2」(其​​中,T1和T2是對象類型以及T2的對齊要求不比T1的對齊要求更嚴格)並且返回到其原始類型時產生原始指針 值。 任何其他此類指針轉換的結果未指定

由於未指定轉換該指針的結果,因此試圖調用其上的函數會導致未定義的行爲。

1

您的代碼不是多態性示例。你只是將對象指針投射到彼此。

你忘了從彼此延伸(繼承)你的類,因爲多態性與遺傳有關。

例如試試這個:

class Class1 
{ 
    public: 
     virtual void f() 
     { 
    cout << "Function f() in Class1\n"; 
     } 

    void g() 
    { 
    cout << "Function g() in Class1\n"; 
    } 
}; 


class Class2 : public Class1 
{ 
    public: 
    virtual void f() 
    { 
    cout << "Function f() in Class2\n"; 
    } 

    void g() 
    { 
    cout << "Function g() in Class2\n"; 
    } 
}; 


class Class3 : public Class2 
{ 
    public: 

    virtual void h() 
    { 
    cout << "Function h() in Class3\n"; 
    } 
    }; 
4

您正在使用邪惡的C風格演員,在這種情況下等效於reinterpret_cast,將指向一個類並假裝指向一個不相關的類。

我期待一些編譯錯誤/崩潰

沒有編譯錯誤,因爲你是故意從檢查的類型轉換防止編譯器 - 這是reinterpret_cast的目的,而原因避免它(和C風格更加如此)除非真的有必要。可能會發生崩潰或任何其他類型的未定義的運行時行爲。

就你的情況而言,類佈局似乎足以使虛擬函數調用成功,即使指針類型完全錯誤。這並不奇怪,因爲你的類定義都非常相似;但這是非常不保證的行爲,原則上它可能會以許多災難性的方式失敗。

如果你想要多態行爲,那麼Class2Class3應該繼承自Class1。指針轉換將在沒有轉換的情況下有效,並且多態行爲將被很好地定義 - 根據對象類型虛擬地調度f(),並且根據指針類型非虛擬地調度g()