1. 程式人生 > >基類與派生類,父類指針指向子類對象

基類與派生類,父類指針指向子類對象

namespace 簡單工廠模式 為什麽 對象創建 簡單工廠 pos 釋放 自己的 分享

先看一段代碼:

 1 #include<iostream>
 2 
 3 using namespace std;
 4 
 5 class Base{
 6 public:
 7     Base() { cout<<"Base Creted"<<endl; }
 8     ~Base() { cout<<"Base Destroyed"<<endl; }
 9 };
10 
11 class Derived: public Base {
12 public:
13     Derived() { cout<<"Derived Created
"<<endl; } 14 ~Derived() { cout<<"Derived Destroyed"<<endl; } 15 }; 16 17 int main() 18 { 19 Base *pB = new Derived(); 20 delete pB; 21 pB = NULL; 22 return 0; 23 }

運行結果如下,情理之中,意料之內:

技術分享圖片

C++創建對象的時候先創建基類部分,然後創建派生部分。析構的時候要反過來了,先釋放子類部分,然後在釋放父類部分。但是這裏只釋放了父類部分,沒有釋放派生類的部分。為什麽呢?

原因很明確:因為之類pB是基類指針,雖然指向的是派生類,只能調用自己的函數,我們是無法通過基類指針調用到子類的成員函數的(除非采用virtual,也就是遲綁定技術)。

虛函數

為了不造成內存泄漏,我們將上面代碼改成:

1 class Base{
2 public:
3     Base() { cout<<"Base Creted"<<endl; }
4     virtual ~Base() { cout<<"Base Destroyed"<<endl; }   //virtual關鍵字很關鍵哦
5 };

運行結果正確了:

技術分享圖片

和之前版本相比,基類的析構函數多了一個 virtual

關鍵字。這樣就使得父類類型的指針可以調用子類的成員函數。虛擬函數就是為了對“如果你以一個基礎類指針指向一個衍生類對象,那麽通過該指針,你只能訪問基礎類定義的成員函數”這條規則反其道而行之的設計。如果你打算將某個類作為基類,那麽一定要定義一個虛析構函數。

虛函數通過動態綁定技術實現了C++的運行時的多態性。讓我們可以通過基類的指針或者引用調用派生類的方法。C++中還有一個多態性是編譯時的多態,通過模版實現。

《Effective C++》條款 07 p40:為多態基類聲明virtual析構函數。

《Effective C++》條款 07 p44:

  • 帶多態性質的base classes應該聲明一個virtual析構函數。如果class帶有任何virtual函數,它就應該擁有一個virtual析構函數
  • Classes的設計目的如果不是作為base classes 使用,或不是為了具備多態性,就不該聲明virtual析構函數。

動態綁定的是怎麽實現的?

1、為每個含義虛函數的類創建一個虛函數表VTable,存到常量區,依次存放虛函數的地址。對於每個派生類來說,如果沒有重寫基類的虛函數,那麽派生類的虛函數表中的函數地址還是基類的那個虛函數地址。

2、為每個含有虛函數的對象創建一個指向VTable的指針VPtr,所以說同類對象的VPtr是一樣的。

3、當基類指針指向派生類時,放生了強制轉換,基類的指針指向了派生類的VPtr,這樣當pBase->func()時,就可以調用派生類的func()了。

4、沒有虛函數的類也就沒有VTable表了,或者這個表為空。這樣基類指針自然調用不到派生類的函數了。

思考:

為何要讓父類指針指向派生類對象?

我覺得是為了實現C++的多態性。比如簡單工廠模式,一開始我們並不知道需要生產哪種產品,也就不知道返回的是什麽類型的產品,此時只能用基類指針去指向返回值。除此之外還沒想到其他的使用場景。

基類與派生類,父類指針指向子類對象