C++構造函數與析構函數能否爲虛函數

結果:

構造函數不能聲明爲虛函數,析構函數可以聲明爲虛函數,而且有時是必須聲明爲虛函數。不建議在構造函數和析構函數裏面調用虛函數。

引用


To construct an object, a constructor needs the exact type of the object it is to create. Consequently, a constructor cannot be virtual. Furthermore, a constructor is not quite an ordinary function, In particular, it interacts with memory management in ways ordinary member functions don’t. Consequently, you cannot have a ponter to a constructor.
— From 《The C++ Progamming Language》15.6.2

其解釋如下:
這就要涉及到C++對象的構造問題了,C++對象在三個地方構建:(1)函數堆棧;(2)自由存儲區,或稱之爲堆;(3)靜態存儲區。無論在那裏構建,其過程都是兩步:首先,分配一塊內存;其次,調用構造函數。好,問題來了,如果構造函數是虛函數,那麼就需要通過vtable 來調用,但此時面對一塊 raw memeory,到哪裏去找 vtable 呢?畢竟,vtable 是在構造函數中才初始化的啊,而不是在其之前。因此構造函數不能爲虛函數。

解釋

構造函數不能聲明爲虛函數的原因是:
1 構造一個對象的時候,必須知道對象的實際類型,而虛函數行爲是在運行期間確定實際類型的。而在構造一個對象時,由於對象還未構造成功。編譯器無法知道對象 的實際類型,是該類本身,還是該類的一個派生類,或是更深層次的派生類。無法確定。。。

2 虛函數的執行依賴於虛函數表。而虛函數表在構造函數中進行初始化工作,即初始化vptr,讓他指向正確的虛函數表。而在構造對象期間,虛函數表還沒有被初 始化,將無法進行。

虛函數的意思就是開啓動態綁定,程序會根據對象的動態類型來選擇要調用的方法。然而在構造函數運行的時候,這個對象的動態類型還不完整,沒有辦法確定它到底是什麼類型,故構造函數不能動態綁定。(動態綁定是根據對象的動態類型而不是函數名,在調用構造函數之前,這個對象根本就不存在,它怎麼動態綁定?)
編譯器在調用基類的構造函數的時候並不知道你要構造的是一個基類的對象還是一個派生類的對象。

析構函數設爲虛函數的作用:
解釋:在類的繼承中,如果有基類指針指向派生類,那麼用基類指針delete時,如果不定義成虛函數,派生類中派生的那部分無法析構。
例:

#include "stdafx.h"
#include "stdio.h"
class A
{
public:
A();
virtual~A();
};
A::A()
{
}

A::~A()
{
printf("Delete class APn");
}
class B : public A
{
public:
B();
~B();
};

B::B()
{ }

B::~B()
{
printf("Delete class BPn");
}
int main(int argc, char* argv[])
{
A *b=new B;
delete b;
return 0;
}

輸出結果爲:Delete class B
Delete class A

如果把A的virtual去掉:那就變成了Delete class A也就是說不會刪除派生類裏的剩餘部分內容,也即不調用派生類的虛函數

因此在類的繼承體系中,基類的析構函數不聲明爲虛函數容易造成內存泄漏。所以如果你設計一定類可能是基類的話,必須要聲明其爲虛函數。正如Symbian中的CBase一樣。

總結:

如果我們定義了一個構造函數,編譯器就不會再爲我們生成默認構造函數了。
2. 編譯器生成的析構函數是非虛的,除非是一個子類,其父類有個虛析構,此時的函數虛特性來自父類。
3. 有虛函數的類,幾乎可以確定要有個虛析構函數。
4. 如果一個類不可能是基類就不要申明析構函數爲虛函數,虛函數是要耗費空間的。
5. 析構函數的異常退出會導致析構不完全,從而有內存泄露。最好是提供一個管理類,在管理類中提供一個方法來析構,調用者再根據這個方法的結果決定下一步的操作。
6. 在構造函數不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到派生類中,既是採用的靜態綁定。顯然的是:當我們構造一個子類的對象時,先調用基類的構造函數,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那麼寫程序,你這麼寫,編譯器也不會報錯。只是你如果這麼寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。
7.在析構函數中也不要調用虛函數。在析構的時候會首先調用子類的析構函數,析構掉對象中的子類部分,然後在調用基類的析構函數析構基類部分,如果在基類的析構函數裏面調用虛函數,會導致其調用已經析構了的子類對象裏面的函數,這是非常危險的。
8. 記得在寫派生類的拷貝函數時,調用基類的拷貝函數拷貝基類的部分,不能忘記了

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