類繼承中構造函數和析構函數的調用

類繼承中構造函數和析構函數的調用

現在,有三個類,類的定義如下

class CA
{
public:
 CA(){cout<<"CA constructor"<<endl;}
 
    ~CA(){cout<<"CA desstructor"<<endl;}

};

class CB:public CA
{
public:
 CB(){cout<<"CB constructor"<<endl;}
 
 ~CB(){cout<<"CB desstructor"<<endl;}
};

class CC:public CB
{
public:
 CC(){cout<<"CC constructor"<<endl;}
 
 ~CC(){cout<<"CC desstructor"<<endl;}
};

CA是爺爺,CB是爸爸,CC是兒子。

那麼任何一本C++的書都會講,構造函數的調用順序是CA CB CC,析構函數的調用順序是CC,CB,CA,什麼???你的書沒講,靠,扔了吧

於是

(1) int main()
{
   CC p ;
}
這個程序運行結果是
CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor

靠,太簡單了,一個雞蛋飛過來了,:(

繼續……………………

(2) 再做第二個試驗之前,先做一點小小修改
~CA(){cout<<"CA desstructor"<<endl;}  ----->>>
virtual  ~CA(){cout<<"CA desstructor"<<endl;}

修改main 代碼如下
int main()
{
   CA * p = new CC();

   delete p;

   return 0;
}

yeah
結果一模一樣哦
CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor

但是如果把virtual  ~CA(){cout<<"CA desstructor"<<endl;}
的virtual 去掉

那麼(2)中的運行結果爲
CA constructor
CB constructor
CC constructor

CA desstructor

只調了CA的哦,出問題了
這樣的話,就會出現基類的構造函數調用了,但是派生類的構造函數沒調用,
對象的派生部分不會被銷燬,這將導致資源泄漏

所以我們在設計一個類的時候,如果類至少擁有一個虛函數,或者說基類被設計用於多態,在這種情況下,
一個派生類的對象可能通過一個基類指針來進行操作,然後進行銷燬,如果這樣的話,那麼這個基類的析構函數要設置成虛擬的,
有些類雖然是基類,但是不是用於多態的,沒有虛函數,沒有被設計成允許經由基類接口派生類對象進行操作,那麼也無需設成虛析構函數,畢竟增加了開銷,
好了,解釋清楚了,我們也知道怎麼做了,繼續試驗


(3)
保留CA中的虛析構函數

修改main 代碼如下
int main()
{
   CB * p = new CC();

   delete p;

   return 0;
}

運行結果
CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor


取消CA中的虛析構函數,那麼,CA,CB,CC中沒有虛析構函數
那麼3中代碼運行結果如下

CA constructor
CB constructor
CC constructor

CB desstructor
CA desstructor

只調到CB的析構哦,

繼續試驗,CA,CB,CC中,只有CB是虛析構函數
3中代碼運行如下

CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor


所以,如果是CB指向派生類,只要CB或者其基類中存在虛析構函數,那麼也是所有的析構函數都調用的了

繼續………………

(4)
修改main 代碼如下
int main()
{
   CA * p = new CC();

   delete p;

   return 0;
}

如果A的析構函數是虛的,那麼情況如2,不多說了

如果是CA的析構函數不是虛的,而CB或者CC的析構函數是虛擬的,那麼在調用delete p;會出現內存錯誤

Expression:_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)  
是在釋放內存的時候出現這樣的錯誤
上網查了一下,_BLOCK_TYPE_IS_VALID是用來檢測內存有效性宏中的一個,這個錯誤說明指針使用出現了問題

後來想了一想,應該是因爲繼承類中出現了虛函數,所以多了一個指向虛函數表的指針,而基類中一個虛函數都沒有,所以也沒有這個指針啦
所以在delete的時候就出現了內存錯,事實證明,這個猜想應該是站得住腳的,在CA中添加一個虛函數,即使的空的虛函數,也不會出現內存錯,
關於這個問題,我想在下次繼續討論吧,這裏不深入進去了,
回到正題,在CA中添加一個空的,任意的虛擬函數以後,運行正確了,
運行結果是
CA constructor
CB constructor
CC constructor

CA desstructor

這與(2)中的情況是一樣的,只要CA的析構函數不是虛擬的,就只能調用CA的析構了


最後來看一種非常Bt的做法
(5)
CA CB CC中的析構函數,誰是虛擬的,無所謂,隨便

修改main 代碼如下
int main()
{
   void * p = new CC();

   delete p;

   return 0;
}

運行結果
CA constructor
CB constructor
CC constructor

下面呢???下面沒有了,暈……………………

這種情況,構造了一個cc的對象,然後呢,沒有調析構函數,直接把申請的內存釋放了,最好不要這樣用咯。


好了,最後,上面,基本上把所有的,我能想到的情況都整理了一下,
總結一下

C1 * p = new C2();
delete p;
這樣的代碼

這裏,C1是C2的基類,C1可能是C2的爸爸,可能是爺爺,可能是爸爸的爺爺,可能是爺爺的爺爺…………………………

那麼首先,調用的構造函數是
從C2的第一個祖先一直到C2………………。和C1是什麼沒關係

在delete p的時候,那麼有以下幾種情況:
1) C1或者C1的祖先(基類)中,含有虛析構函數,那麼調用的析構函數的順序是從C2一直到C2的第一個祖先
2)如果C1或者C1的祖先中,沒有一個是類是含有虛析構函數的,那麼調用的是從C1一直到C1(也是C2的)的第一個祖先的
3)如果C1是void,那就什麼析構都不調用了。

如果一個類,作爲多態的基類,那麼儘量把析構函數聲明成虛擬的,不然………………,

 

好了,就此打住吧,關於4中的內存錯誤,下次再談論吧,項目要開始了,又要忙了,哭去了……………………


參考書籍
《Effective C++》  

發佈了6 篇原創文章 · 獲贊 3 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章