C++中析構(gòu)函數(shù)為何是虛函數(shù)
析構(gòu)函數(shù)為什么是虛函數(shù)
虛構(gòu)函數(shù)是虛函數(shù)的情況只需要在特定場景下出現(xiàn)即可,正常情況下不必要弄成虛函數(shù)。
如果基類的析構(gòu)函數(shù)不是虛函數(shù),在特定情況下會(huì)導(dǎo)致派生來無法被析構(gòu)。
- 情況1:用派生類類型指針綁定派生類實(shí)例,析構(gòu)的時(shí)候,不管基類析構(gòu)函數(shù)是不是虛函數(shù),都會(huì)正常析構(gòu)
- 情況2:用基類類型指針綁定派生類實(shí)例,析構(gòu)的時(shí)候,如果基類析構(gòu)函數(shù)不是虛函數(shù),則只會(huì)析構(gòu)基類,不會(huì)析構(gòu)派生類對象,從而造成內(nèi)存泄漏。為什么???因?yàn)槲鰳?gòu)的時(shí)候如果沒有虛函數(shù)的動(dòng)態(tài)綁定功能,就只根據(jù)指針的類型來進(jìn)行的,而不是根據(jù)指針綁定的對象來進(jìn)行,所以只是調(diào)用了基類的析構(gòu)函數(shù);如果基類的析構(gòu)函數(shù)是虛函數(shù),則析構(gòu)的時(shí)候就要根據(jù)指針綁定的對象來調(diào)用對應(yīng)的析構(gòu)函數(shù)了。
構(gòu)造函數(shù)為什么不能出虛函數(shù)
因?yàn)轭惖奶摵瘮?shù)表指針是在構(gòu)造函數(shù)中初始化的,這時(shí)候如果構(gòu)造函數(shù)本身是虛函數(shù),又應(yīng)該由誰來初始化它的虛函數(shù)指針呢,所以構(gòu)造函數(shù)不能是虛函數(shù)。
為什么構(gòu)造函數(shù)和析構(gòu)函數(shù)都不能調(diào)用虛函數(shù)
?? ?#include <iostream.h> ? ? using namespace std; ? ? class Base{ ? ? ? ? public: ? ? ? ? ? ? Base(){ ? ? ? ? ? ? ? ? cout << "Base::Base()\n"; ? ? ? ? ? ? ? ? fun(); ? ? ? ? ? ? } ? ? ? ? ? ? virtual ~Base(){ ? ? ? ? ? ? ? ? cout << "Base::Base()\n"; ? ? ? ? ? ? ? ? fun(); ? ? ? ? ? ? } ? ? ? ? ? ? virtual void fun(){ ? ? ? ? ? ? ? ? cout << "Base::fun() virtual\n"; ? ? ? ? ? ? } ? ? }; ? ? // 派生類 ? ? class Derive: public Base{ ? ? ? ? public: ? ? ? ? ? ? Derive(){ ? ? ? ? ? ? ? ? cout << "Derive::Derive()\n"; ? ? ? ? ? ? ? ? fun(); ? ? ? ? ? ? } ? ? ? ? ? ? ~Derive(){ ? ? ? ? ? ? ? ? cout << "Derive::Derive()\n"; ? ? ? ? ? ? ? ? fun(); ? ? ? ? ? ? } ? ? ? ? ? ? virtual void fun(){ ? ? ? ? ? ? ? ? cout << "Derive::fun() virtual\n"; ? ? ? ? ? ? } ? ? }; ? ? int main() ? ? { ? ? ? ? Base *bd = new Derive(); ?// 基類Base的指針bd指向的是派生類Derive的對象 ? ? ? ? delete bd; ? ? ? ? return 0; ? ? }
對于上述情況,基類指針指向派生類對象。構(gòu)造時(shí),先調(diào)用基類Base的構(gòu)造函數(shù),此時(shí)構(gòu)函數(shù)中調(diào)用基類中的fun()函數(shù),此時(shí)虛函數(shù)的動(dòng)態(tài)綁定機(jī)制并沒有會(huì)生效,這是因?yàn)榇藭r(shí)的派生類還不存在。析構(gòu)時(shí),先析構(gòu)派生類,派生類中的fun()函數(shù)調(diào)用的是自己的fun(),然后析構(gòu)基類Base,基類析構(gòu)函數(shù)中的fun()調(diào)用的是基類Base自己的fun()函數(shù),這里虛函數(shù)的動(dòng)態(tài)綁定機(jī)制也沒有生效,因?yàn)榇藭r(shí)派生類已經(jīng)不存在了。
不要在構(gòu)造函數(shù)中調(diào)用虛函數(shù)的原因:因?yàn)楦割悓ο髸?huì)在子類之前進(jìn)行構(gòu)造,此時(shí)子類部分的數(shù)據(jù)成員還未初始化, 因此調(diào)用子類的虛函數(shù)是不安全的,故而C++不會(huì)進(jìn)行動(dòng)態(tài)聯(lián)編。
不要在析構(gòu)函數(shù)中調(diào)用虛函數(shù)的原因:析構(gòu)函數(shù)是用來銷毀一個(gè)對象的,在銷毀一個(gè)對象時(shí),先調(diào)用子類的析構(gòu)函數(shù),然后再調(diào)用基類的析構(gòu)函數(shù)。所以在調(diào)用基類的析構(gòu)函數(shù)時(shí),派生類對象的數(shù)據(jù)成員已經(jīng)“銷毀”,這個(gè)時(shí)再調(diào)用子類的虛函數(shù)已經(jīng)沒有意義了。
c++基類的析構(gòu)函數(shù)為虛函數(shù)的原因
原因
在實(shí)現(xiàn)多態(tài)時(shí), 當(dāng)用基類指針操作派生類, 在析構(gòu)時(shí)候防止只析構(gòu)基類而不析構(gòu)派生類。
例子
(1):
#include<iostream> using namespace std; class Base{ public: ? ?Base() {}; ? ~Base() {cout << "Output from the destructor of class Base!" << endl;}; ? ? void DoSomething() { cout << "Do something in class Base!" << endl; }; }; ? class Derived : public Base{ public: ? ?Derived() {}; ? ~Derived() { cout << "Output from the destructor of class Derived!" << endl; }; ? ? void DoSomething() { cout << "Do something in class Derived!" << endl; }; }; ? int ?main() ? ?{? ? Derived* p = new Derived; ?p->DoSomething(); ?delete p; ?return 0; }
運(yùn)行結(jié)果:
Do something in class Derived!
Output from the destructor of class Derived!
Output from the destructor of class Base!
代碼中基類的析構(gòu)函數(shù)不是虛函數(shù),在main函數(shù)中用繼承類的指針去操作繼承類的成員,釋放指針P的過程是:先釋放繼承類的資源,再釋放基類資源。
(2):
?#include<iostream> using namespace std; class Base{ public: ? ?Base() {}; ? ~Base() {cout << "Output from the destructor of class Base!" << endl;}; ? ? void DoSomething() { cout << "Do something in class Base!" << endl; }; }; class Derived : public Base{ public: ? ?Derived() {}; ? ~Derived() { cout << "Output from the destructor of class Derived!" << endl; }; ? ? void DoSomething() { cout << "Do something in class Derived!" << endl; }; }; ? int ?main(){? ? Base* p = new Derived; ?p->DoSomething(); ?delete p; ?return 0; ?}
運(yùn)行結(jié)果:
Do something in class ClxBase!
Output from the destructor of class ClxBase!
代碼中基類的析構(gòu)函數(shù)同樣不是虛函數(shù),不同的是在main函數(shù)中用基類的指針去操作繼承類的成員,釋放指針P的過程是:只釋放基類的資源,而沒有調(diào)用繼承類的析構(gòu)函數(shù)。 調(diào)用DoSomething()函數(shù)執(zhí)行的也是基類定義的函數(shù)。
一般情況下,這樣的刪除只能夠刪除基類對象,而不能刪除子類對象,形成了刪除一半形象,造成內(nèi)存泄漏。
在公有繼承中,基類對派生類及其對象的操作,只能影響到那些從基類繼承下來的成員。如果想要用基類對非繼承成員進(jìn)行操作,則要把基類的這個(gè)函數(shù)定義為虛函數(shù)。 析構(gòu)函數(shù)自然也應(yīng)該如此:如果它想析構(gòu)子類中的重新定義或新的成員及對象,當(dāng)然也應(yīng)該聲明為虛的。
(3):
#include<iostream> using namespace std; class Base{ public: ? ?Base() {}; ? virtual ~Base() {cout << "Output from the destructor of class Base!" << endl;}; ? ? virtual void DoSomething() { cout << "Do something in class Base!" << endl; }; }; class Derived : public Base{ public: ? ?Derived() {}; ? ~Derived() { cout << "Output from the destructor of class Derived!" << endl; }; ? ? void DoSomething() { cout << "Do something in class Derived!" << endl; }; }; ? int ?main(){? ? Base* p = new Derived; ?p->DoSomething(); ?delete p; ?return 0; ?}
運(yùn)行結(jié)果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
代碼中基類的析構(gòu)函數(shù)被定義為虛函數(shù),在main函數(shù)中用基類的指針去操作繼承類的成員,釋放指針P的過程是:釋放了繼承類的資源,再調(diào)用基類的析構(gòu)函數(shù)。調(diào)用DoSomething()函數(shù)執(zhí)行的也是繼承類定義的函數(shù)。
小結(jié):基類指針可以指向派生類的對象(多態(tài)性),如果刪除該指針delete p;就會(huì)調(diào)用該指針指向的派生類析構(gòu)函數(shù),而派生類的析構(gòu)函數(shù)又自動(dòng)調(diào)用基類的析構(gòu)函數(shù),這樣整個(gè)派生類的對象完全被釋放。
如果析構(gòu)函數(shù)不被聲明成虛函數(shù),則編譯器實(shí)施靜態(tài)綁定,在刪除基類指針時(shí),只會(huì)調(diào)用基類的析構(gòu)函數(shù)而不調(diào)用派生類析構(gòu)函數(shù),這樣就會(huì)造成派生類對象析構(gòu)不完全。所以,將析構(gòu)函數(shù)聲明為虛函數(shù)是十分必要的。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
計(jì)時(shí)器的time_t和clock_t 的兩種實(shí)現(xiàn)方法(推薦)
下面小編就為大家?guī)硪黄?jì)時(shí)器的time_t和clock_t 的兩種實(shí)現(xiàn)方法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10基于Matlab制作一個(gè)不良圖片檢測系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了如何基于Matlab制作一個(gè)不良圖片檢測系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起了解一下2022-07-07關(guān)于讀取popen輸出結(jié)果時(shí)未截?cái)嘧址畬?dǎo)致的命令行注入詳解
這篇文章主要給大家介紹了關(guān)于讀取popen輸出結(jié)果時(shí)未截?cái)嘧址畬?dǎo)致的命令行注入的相關(guān)資料,文中通過圖文及示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03C++ DLL動(dòng)態(tài)庫的創(chuàng)建與調(diào)用(類庫,隱式調(diào)用)
本文主要介紹了C++ DLL動(dòng)態(tài)庫的創(chuàng)建與調(diào)用(類庫,隱式調(diào)用),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05- 形參出現(xiàn)在函數(shù)定義中,在整個(gè)函數(shù)體內(nèi)都可以使用, 離開該函數(shù)則不能使用。實(shí)參出現(xiàn)在主調(diào)函數(shù)中,進(jìn)入被調(diào)函數(shù)后,實(shí)參變量也不能使用,形參和實(shí)參的功能是作數(shù)據(jù)傳送。發(fā)生函數(shù)調(diào)用時(shí), 主調(diào)函數(shù)把實(shí)參的值傳送給被調(diào)函數(shù)的形參從而實(shí)現(xiàn)主調(diào)函數(shù)向被調(diào)函數(shù)的數(shù)據(jù)傳送2021-11-11
一起來學(xué)習(xí)C語言的字符串轉(zhuǎn)換函數(shù)
這篇文章主要為大家詳細(xì)介紹了C語言的字符串轉(zhuǎn)換函數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02C語言實(shí)現(xiàn)清空指定文件夾中所有文件的方法
這篇文章主要介紹了C語言實(shí)現(xiàn)清空指定文件夾中所有文件的方法,實(shí)例分析了C語言實(shí)現(xiàn)文件刪除的相關(guān)技巧,需要的朋友可以參考下2015-06-06Qt中CQGUI框架之陰影圓角窗口實(shí)現(xiàn)
這篇文章主要介紹了Qt中CQGUI框架之陰影圓角窗口實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03