构造函数和析构函数能否声明为虚函数?
構(gòu)造函數(shù)不能聲明為虛函數(shù),析構(gòu)函數(shù)可以聲明為虛函數(shù),而且有時(shí)是必須聲明為虛函數(shù)。
不建議在構(gòu)造函數(shù)和析構(gòu)函數(shù)里面調(diào)用虛函數(shù)。
構(gòu)造函數(shù)不能聲明為虛函數(shù)的原因是:
解釋一:所謂虛函數(shù)就是多態(tài)情況下只執(zhí)行一個(gè)。而從繼承的概念來講,總是要先構(gòu)造父類對象,然后才能是子類對象。如果構(gòu)造函數(shù)設(shè)為虛函數(shù),那么當(dāng)你在構(gòu)造父類的構(gòu)造函數(shù)時(shí)就不得不顯示的調(diào)用構(gòu)造。還有一個(gè)原因就是為了防錯(cuò),試想如果你在子類中一不小心重寫了個(gè)跟父類構(gòu)造函數(shù)一樣的函數(shù),那么你的父類的構(gòu)造函數(shù)將被覆蓋,也即不能完成父類的構(gòu)造.就會(huì)出錯(cuò)。
解釋二:虛函數(shù)的主要意義在于被派生類繼承從而產(chǎn)生多態(tài)。派生類的構(gòu)造函數(shù)中,編譯器會(huì)加入構(gòu)造基類的代碼,如果基類的構(gòu)造函數(shù)用到參數(shù),則派生類在其構(gòu)造函數(shù)的初始化列表中必須為基類給出參數(shù),就是這個(gè)原因。
虛函數(shù)的意思就是開啟動(dòng)態(tài)綁定,程序會(huì)根據(jù)對象的動(dòng)態(tài)類型來選擇要調(diào)用的方法。然而在構(gòu)造函數(shù)運(yùn)行的時(shí)候,這個(gè)對象的動(dòng)態(tài)類型還不完整,沒有辦法確定它到底是什么類型,故構(gòu)造函數(shù)不能動(dòng)態(tài)綁定。(動(dòng)態(tài)綁定是根據(jù)對象的動(dòng)態(tài)類型而不是函數(shù)名,在調(diào)用構(gòu)造函數(shù)之前,這個(gè)對象根本就不存在,它怎么動(dòng)態(tài)綁定?)
編譯器在調(diào)用基類的構(gòu)造函數(shù)的時(shí)候并不知道你要構(gòu)造的是一個(gè)基類的對象還是一個(gè)派生類的對象。
析構(gòu)函數(shù)設(shè)為虛函數(shù)的作用:????
解釋:在類的繼承中,如果有基類指針指向派生類,那么用基類指針delete時(shí),如果不定義成虛函數(shù),派生類中派生的那部分無法析構(gòu)。
#include "stdafx.h" #include "stdio.h" class A { public: A(); virtual ~A(); }; A::A() { } A::~A() { printf("Delete class AP/n"); } class B : public A { public: B(); ~B(); }; B::B() { } B::~B() { printf("Delete class BP/n"); } int main(int argc, char* argv[]) { A *b=new B; delete b; return 0; }輸出結(jié)果為:Delete?? class?? B????????
??????????????????????? Delete?? class?? A??
如果把A的virtual去掉:那就變成了Delete?? class?? A
因此在類的繼承體系中,基類的析構(gòu)函數(shù)不聲明為虛函數(shù)容易造成內(nèi)存泄漏。所以如果你設(shè)計(jì)一定類可能是基類的話,必須要聲明其為虛函數(shù)。正如Symbian中的CBase一樣。Note:
1. 如果我們定義了一個(gè)構(gòu)造函數(shù),編譯器就不會(huì)再為我們生成默認(rèn)構(gòu)造函數(shù)了。
2. 編譯器生成的析構(gòu)函數(shù)是非虛的,除非是一個(gè)子類,其父類有個(gè)虛析構(gòu),此時(shí)的函數(shù)虛特性來自父類。
3. 有虛函數(shù)的類,幾乎可以確定要有個(gè)虛析構(gòu)函數(shù)。
4. 如果一個(gè)類不可能是基類就不要申明析構(gòu)函數(shù)為虛函數(shù),虛函數(shù)是要耗費(fèi)空間的。
5. 析構(gòu)函數(shù)的異常退出會(huì)導(dǎo)致析構(gòu)不完全,從而有內(nèi)存泄露的問題。最好是提供一個(gè)管理類,在管理類中提供一個(gè)方法來析構(gòu),調(diào)用者再根據(jù)這個(gè)方法的結(jié)果決定下一步的操作。
6. 在構(gòu)造函數(shù)不要調(diào)用虛函數(shù)。在基類構(gòu)造的時(shí)候,虛函數(shù)是非虛,不會(huì)走到派生類中,既是采用的靜態(tài)綁定。顯然的是:當(dāng)我們構(gòu)造一個(gè)子類的對象時(shí),先調(diào)用基類的構(gòu)造函數(shù),構(gòu)造子類中基類部分,子類還沒有構(gòu)造,還沒有初始化,如果在基類的構(gòu)造中調(diào)用虛函數(shù),如果可以的話就是調(diào)用一個(gè)還沒有被初始化的對象,那是很危險(xiǎn)的,所以C++中是不可以在構(gòu)造父類對象部分的時(shí)候調(diào)用子類的虛函數(shù)實(shí)現(xiàn)。但是不是說你不可以那么寫程序,你這么寫,編譯器也不會(huì)報(bào)錯(cuò)。只是你如果這么寫的話編譯器不會(huì)給你調(diào)用子類的實(shí)現(xiàn),而是還是調(diào)用基類的實(shí)現(xiàn)。
7. 在析構(gòu)函數(shù)中也不要調(diào)用虛函數(shù)。在析構(gòu)的時(shí)候會(huì)首先調(diào)用子類的析構(gòu)函數(shù),析構(gòu)掉對象中的子類部分,然后在調(diào)用基類的析構(gòu)函數(shù)析構(gòu)基類部分,如果在基類的析構(gòu)函數(shù)里面調(diào)用虛函數(shù),會(huì)導(dǎo)致其調(diào)用已經(jīng)析構(gòu)了的子類對象里面的函數(shù),這是非常危險(xiǎn)的。
8. 記得在寫派生類的拷貝函數(shù)時(shí),調(diào)用基類的拷貝函數(shù)拷貝基類的部分,不能忘記了。
?
如果一個(gè)類是作為基類使用,那么他的虛構(gòu)函數(shù)一定要是虛的,即用virtual關(guān)鍵字(參數(shù)為零則為純虛函數(shù)). ? 否則會(huì)有內(nèi)存泄漏(很重要),因?yàn)楫?dāng)用基類的指針刪除一個(gè)派生類的對象時(shí),要調(diào)用派生類的析構(gòu)函數(shù).但是 ? 其子類或者子子類可以的析構(gòu)函數(shù)可以是虛函數(shù),也可以不是虛函數(shù).(不加virtual 關(guān)鍵字則不會(huì)調(diào)用派生 ? 類的析構(gòu)函數(shù),而上面用了ClxBase *pTest = new ClxDerived;語句也就是new的ClxDerived對象沒有?? 銷毀,所以產(chǎn)生內(nèi)存泄漏) 2.類中的虛函數(shù),如果一個(gè)類中的函數(shù)被聲明成為虛函數(shù),那么其子類不用在聲明為虛函數(shù)(當(dāng)子類還有子類時(shí)), ? 也可以聲明為虛函數(shù).結(jié)果是一樣的.同虛析構(gòu)函數(shù)的道理是一樣的.當(dāng)然,并不是要把所有類的析構(gòu)函數(shù)都寫 ? 成虛函數(shù)。因?yàn)楫?dāng)類里面有虛函數(shù)的時(shí)候,編譯器會(huì)給類添加一個(gè)虛函數(shù)表,里面來存放虛函數(shù)指針,這樣就 ? 會(huì)增加類的存儲空間。所以,只有當(dāng)一個(gè)類被用來作為基類的時(shí)候,才把析構(gòu)函數(shù)寫成虛函數(shù)。
具體例子:
#include "iostream.h"class ClxBase {public:ClxBase() {};virtual ~ClxBase() { cout << "Output from the destructor of class ClxBase!" << endl; };virtual void DoSomething() { cout << "Do something in class ClxBase!"<< endl; }; };class ClxDerived : public ClxBase {public:ClxDerived() {};virtual~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };//此處的virtual可以去掉virtual void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };//此處的virtual可以去掉 };class ClxThrived : public ClxDerived { public:ClxThrived(){};virtual ~ClxThrived(){cout << "Output from the destructor of class ClxThrived!" << endl;};//此處的virtual可以去掉virtual void DoSomething(){cout << "Do something in class ClxThrived!" << endl;}//此處的virtual可以去掉 }; void main() {ClxBase *pTest1 = new ClxBase;pTest1->DoSomething();delete pTest1;//1 ClxBase *pTest2 = new ClxDerived;pTest2->DoSomething();delete pTest2;//2 用基類的指針刪除一個(gè)派生類的對象時(shí) ClxDerived *pTest3 = new ClxDerived;pTest3->DoSomething();delete pTest3;//3 ClxBase *pTest4 = new ClxThrived;pTest4->DoSomething();delete pTest4;//4 用基類的指針刪除一個(gè)派生類的對象時(shí) ClxDerived *pTest5 = new ClxThrived;pTest5->DoSomething();delete pTest5;//5 用基類的指針刪除一個(gè)派生類的對象時(shí) ClxThrived *pTest6 = new ClxThrived;pTest6->DoSomething();delete pTest6;//6 }?
轉(zhuǎn)載于:https://www.cnblogs.com/followyourdream/p/3397387.html
總結(jié)
以上是生活随笔為你收集整理的构造函数和析构函数能否声明为虚函数?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [算法 笔记]堆排序(续)
- 下一篇: 文本域字数限制统计(不区分中英文 符号)