2011-10-12 51 views
1

我有這樣的代碼:虛擬關鍵字使得人跡罕至的方法從派生的對象

#include <iostream> 

class Super{ 
public: 
    virtual void showName(); 
}; 

class Special1 : public Super { 
public: 
    void showName(); 
    void sayHello(); 
}; 

class Special2 : public Super { 
public: 
    void showName(); 
    void sayGoodbye(); 
}; 

void Super::showName() { 
    std::cout << "I'm super!" << std::endl; 
} 

void Special1::showName() { 
    std::cout << "I'm special1" << std::endl; 
} 

void Special1::sayHello() { 
    std::cout << "Hello" << std::endl; 
} 

void Special2::showName() { 
    std::cout << "I'm special2" << std::endl; 
} 

void Special2::sayGoodbye() { 
    std::cout << "Goodbye" << std::endl; 
} 

int main() { 
    Super *oSpec=new Super; 

    Special1 *o1=static_cast<Special1 *>(oSpec); 
    Special2 *o2=static_cast<Special2 *>(oSpec); 

    oSpec->showName(); 
    o1->showName(); 
    o2->showName(); 

    o1->sayHello(); 
    o2->sayGoodbye(); 

    delete oSpec; 

    return 0; 
} 

當我運行它,它顯示了這個輸出:

I'm super! 
I'm super! 
I'm super! 
Hello 
Goodbye 

但是,如果我刪除virtual關鍵字來自Super類的聲明:

class Super{ 
public: 
    /*virtual*/ void showName(); 
}; 

輸出成爲正確之一:

I'm super! 
I'm special1 
I'm special2 
Hello 
Goodbye 

然後,我的問題是,爲什麼virtual關鍵字的存在使得指針o1o2運行的方法Super::showName(),而不是Special1::showName()Special2::showName()

+0

使用上面的static_cast <>是未定義的行爲。該代碼已損壞,僅適用於編譯器及其當前標誌的奇想。 –

+0

代碼會導致未定義的行爲,除非指向的對象實際上是一個「Special#」實例,否則不能將'Super *'強制轉換爲'Special#*'對象,而在您的情況下則不是。 –

回答

2

輸出與virtual更接近正確,並且與此不正確(除非您確實需要)。轉換不會改變對象的類型,只是指針的類型。它們仍然是Super,因此它應該運行Super::showName

將一個指針類型投射到另一個指針類型不會更改指向的東西的類型。它怎麼可能?虛函數的全部要點是能夠調用「通用」指針的方法並獲得正確的派生類方法。

爲什麼使用虛擬功能的典型例子是音樂家。通過調用Play方法,每個Musician *它可以通過一個函數,導致整個管絃樂隊播放。對於Pianist,必須調用Pianist::Play

通常情況下,編譯器會指出在編譯時調用哪個函數 - 早期綁定。編譯器唯一知道的信息就是指針的類型。關鍵字virtual會導致綁定在運行時延遲發生,此時類實際成員類型已知。

順便說一句,您仍然可以通過使用範圍覆蓋來調用基類方法。例如o1->Super::showName();

事實上,你聲稱'正確'的結果是災難性的。運行Special1::showName()this指針指向的對象不是類型Special1(或從中派生的東西)是未定義的行爲,並且很容易導致崩潰。

+2

由於程序具有未定義的行爲,您無法真正說出任何一個輸出是「正確的」。 –

+0

@Mike Seymour:你是對的。您不能通過指向類成員不是實例的類型的指針訪問類成員。 (我把這一點加到我的答案上,謝謝。) –

+0

我真的不喜歡你使用上面的'正確'這個詞。未定義的行爲沒有正確的行爲。 –

2

因爲main中的代碼是錯誤的。對象都說「我超級」,因爲對他們來說(實際上,在後臺),大多數派生類型仍然是super。這就是他們的創造。

你的靜態投打破了所有的規則,是未定義行爲(任何事情都有可能發生),因爲你告訴編譯器o1Special1,而事實上,它是不是一種類型的Special1。當創建了一堆衍生對象,並將它們存儲在一個指針/容器保持指針指向對象

virtual函數通常是有用的。而不是相反。您想要創建類型爲Special1Special2的指針並將它們存儲在指向super的指針中。

2

您的演員(Special1 *o1=static_cast<Special1 *>(oSpec);)在這兩種情況下都是未定義的行爲,因此就語言而言,任何輸出都是可以接受的。你已經使用了一個類型爲Super的對象,並對編譯器說謊,告訴它它確實是一個派生類。在這種情況下,發生的情況是,您仍然可以獲得父類Super的結果。

1

您的代碼只是未定義。
試圖說明爲什麼它以特定的方式工作是沒用的。

這裏:

Super *oSpec=new Super; 

type中超的目標oSpec點。

因此在這些陳述中。演員陣容有未定義的行爲,因爲oSpec指向的對象既不是特殊1也不是特殊2)。

Special1 *o1=static_cast<Special1 *>(oSpec); 
Special2 *o2=static_cast<Special2 *>(oSpec); 

如果使用dynamica_cast <>(像你應該鑄造起來的類層次結構時)。你會發現結果是NULL。

Special1 *o1=dynamic_cast<Special1 *>(oSpec); 
Special2 *o2=dynamic_cast<Special2 *>(oSpec); 

std::cout << "O1(" << (void*)o1 << ") O2(" << (void*)o2 << ")\n"; 

這將顯示兩個指針都是NULL。