2015-10-26 122 views
-1

下面的代碼段工作正常:虛函數和繼承在C++

#include <iostream> 
#include <string> 

using namespace std; 

class HelpInterface { 
public: 
    void getHelp(); 
}; 

class Applicaiton : public HelpInterface { 
public: 
    void getHelp() { 
     cout << "General help"; 
    } 
}; 

int main(void) { 
    Applicaiton applicaiton; 
    applicaiton.getHelp(); 
} 

請在幫助界面類getHelp功能虛擬,我會得到一個鏈接錯誤:

class HelpInterface { 
public: 
    virtual void getHelp(); 
}; 

如果我做的getHelp的空實現,如下面的東西將再次工作:

class HelpInterface { 
public: 
    virtual void getHelp() {}; 
}; 

有人可以幫我理解nd爲什麼虛擬引發鏈接器錯誤,除非我在基類中有getHelp的實現,並且爲什麼沒有實現的非虛函數工作得很好?在這個例子中,基本函數永遠不會被調用。

下面是VS2013與鏈接錯誤截圖: Screenshot from VS2013

+1

無法重現。 http://ideone.com/NrAvM1 – songyuanyao

+0

不知道爲什麼,但虛擬關鍵字不應導致鏈接器錯誤。此代碼的工作,並編制http://coliru.stacked-crooked.com/a/e64d407a206d2979 – Krypton

+0

@songyuanyao 不爲我工作的代碼,如果我使用: 類幫助界面{ 市民: 虛擬無效getHelp (); }; – MrAliB

回答

3

如果你想你的基類的方法是虛擬的,並沒有提供實現,則必須將其設爲零這樣的:

class HelpInterface { 
public: 
    virtual void getHelp() = 0; 
}; 

這被稱爲純粹的虛擬方法。它具有使您的類抽象並強制其所有派生類提供該方法的實現的效果。因此,請注意,您將不再能夠創建基類的實例,因爲它是抽象的。

+0

請在您的答案中包含正確的術語(抽象和純虛擬) – CoffeeandCode

1

當一個方法在基類中但不是虛擬的時,如果你沒有對基類型的指針/引用進行顯式調用,鏈接器不會實際引用該方法的實現(在派生類的實例上)或基類型的實例。

虛擬功能可以通過許多不同的方式實現,並且是特定於實現的。最常見的一種是使用虛擬表格。 (也可以通過虛擬方法表,虛擬函數表,虛擬調用表,調度表,vtable或vftable)知道,我很確定你的編譯器(VS2013)使用這種實現虛函數的方法。

虛擬表是虛擬成員函數的函數指針表。類實例將包含一個指向該類所屬表的指針。

當你使一個函數虛擬時,鏈接器試圖把它放到這種類型的虛擬表中。不管你是否調用它或實例化一個基類(也是一個實現特定的細節)。

正如qexyn已經回答的那樣,爲了避免在虛函數聲明後面添加= 0來聲明該方法爲純虛擬方法。這告訴鏈接器在類的虛函數表中放置一個空指針。您還可以聲明虛擬函數純虛擬並提供實現。這迫使派生類實現該功能,但允許他們選擇使用默認方法。

+0

「虛擬表」是特定於實現的,標準中沒有任何內容說明必須像這樣實現「虛擬」功能。 – CoffeeandCode

+0

@CoffeeandCode,是的,我不是故意暗示它是。我已經添加了一些澄清和解釋。 –

0

原因是您的基類定義使用間接方法獲取Application的實際功能。

這通常是用一個函數指針實現的,但無論如何可能有一個派生類不覆蓋基類的實現。

雖然this isn't usually reproducible這僅僅是因爲成員函數是隱式聲明inline當執行是可見的優化編譯器會做到這些;內聯函數。 The proof is in the optimization.

你想要什麼,是要確保你基地的每個派生類實現getHelp()。這是一種常見的習慣用法,是核心語言功能。你想要一個「純虛函數」。這將使您的基類成爲「抽象基類」,它實際上是面向對象的jibber-jabber中的「接口」的同義詞。

在C++中做到這一點的方法是使用後,所有的成員函數預選賽的特殊成員函數特定的語法(我在這裏使用後的返回類型,因爲我認爲他們是美麗的):

class base{ 
    public: 
     virtual auto func() -> void = 0; 
}; 

這指定base是一個具有純虛函數func()的抽象基類,從它派生的所有類都將實現。

你的情況,你可以這樣寫:

class HelperInterface{ 
    public: 
     virtual void getHelp() = 0; // probably want const qualifier 
}; 

而剛剛離開Application,因爲它是。

你每天都在學點東西,是吧?

+0

你說得對,這與優化有關。 -O1將沒有問題,而-O0會導致鏈接器錯誤。我很好奇的是這個原因。使用-O0,如果我明確內聯getHelp函數,事情似乎就行得通。 inline virtual void getHelp(); 所以,我很好奇爲什麼當函數不內聯時,鏈接器錯誤發生。 – MrAliB

+0

@MrAliB,因爲即使使用明確的「inline」,如果您編譯時沒有進行優化,它可能不會被內聯。 GCC的內聯有點像宏替換,所以這就是優化版本工作的原因。 – CoffeeandCode