更改記錄,使level()
虛擬
您寫道:
Record *r = new Student();
該行後,編譯器認爲r
是一個指針到任何一個Record
或者一些Record
- 派生類(它是),但它只知道爲Record
指定的接口。在Record
中沒有virtual
level()
功能,所以不能通過Record
接口訪問Student
的關卡功能。只需添加這樣的功能Record
,你會沒事:
virtual int level() { return 0; } // Student may override implementation
或
virtual int level() = 0; // Student MUST override implementation
一種替代方案:檢查是否記錄*解決了學生
我上面說的。 ..
沒有virtual
level()
功能Record
,所以不能通過Record
界面訪問Student
的關卡功能。
...並告訴你如何可以添加到Record
接口,但另一種方法是重新獲得訪問Student
接口,如:
if (Student* p = dynamic_cast<Student*>(r))
std::cout << "Level " << p->level() << '\n';
第一線檢查是否Record* r
碰巧指向一個Student
(當然在你的代碼中它總是這樣,但是想象你在一個接受Record*
的函數中,或者正在循環一個這樣的指針的容器,其中一些真的是Student
s而其他的不是)。如果是這樣,返回的指針可用於訪問它作爲Student
對象,具有任何額外的功能/成員可用(和潛在的限制,如果某些Record
功能隱藏在某種方式)。
這種方法一般是令人難以接受的,因爲它引出了一個問題:「爲什麼我們進行治療Student
爲Record
,如果我們需要知道的‘水平’等Record
派生類型甚至沒有等級的概念? 」。不過,有時候會發生類似的情況。添加virtual
level
功能到Record
如果Student
是(其中一個)它將具有有意義的值的唯一派生類,那麼它並不理想:這就是所謂的胖接口 - 您會發現幾個討論如果您有副本,則可以使用C++編程語言。
(sasha.sochka的回答是首先要提到的dynamic_cast
選項 - 請給予好評)
基類應該有虛析構
按克里斯的評論,您應該添加到Record
:
virtual ~Record() { }
這確保派生類的析構函數實現在派生對象爲delete
時被調用d使用基於類的函數,類指針(例如,如果在main()
的底部添加delete r;
)。 (編譯器仍然會確保之後調用基類析構函數)。
這是未定義的行爲,如果你不這樣做,最好你會發現在派生類中添加的任何額外的數據成員沒有他們的數據成員調用......對於int
是無害的,但對於說std::string
它可能會泄漏內存,甚至不能鎖定程序以後掛起。當然,依賴最好的未定義行爲並不是一個好主意;-)但是我認爲理解什麼是絕對不會發生的,除非你創建了基礎析構函數virtual
。在學生
推薦小改進,如果您level
在virtual
Record
,然後使其更清晰的Student
類的讀者,level()
是從基類virtual
功能的實現,可以使用override
關鍵字,如果你有適當C++ 11功能的編譯器:
int level() override
{
return level_;
}
這會給你一個編譯器錯誤,如果它找不到匹配(非const
)virtual int level()
在基類,所以它可以避免一些偶爾的故障排除。如果您覺得它具有文檔值(對於C++ 03來說特別好(其中override
不是一種選項),但您可能還會重複使用virtual
關鍵字),但它不會產生任何功能差異 - 只要它的功能保持爲virtual
(隱式或顯式地)從基類中覆蓋虛擬函數。
你'Record'類應該有虛析構函數。你的Student類應該有一個隱式定義的類。並且你的所有函數都不需要分號。 – chris
我認爲有一個getType()方法通常不是個好主意。這並不總是一件壞事,但如果可能的話儘量避免它。不要忘了爲繼承設計的類中的虛擬析構函數,對於虛擬析構函數的建議,這非常重要 – SpongeBobFan
@chris:+1,但是'Student'是否具有隱式定義的析構函數並不重要。分號,*顫抖* ;-)。 –