C++中析構函數為何是虛函數
析構函數為什么是虛函數
虛構函數是虛函數的情況只需要在特定場景下出現即可,正常情況下不必要弄成虛函數。
如果基類的析構函數不是虛函數,在特定情況下會導致派生來無法被析構。
- 情況1:用派生類類型指針綁定派生類實例,析構的時候,不管基類析構函數是不是虛函數,都會正常析構
- 情況2:用基類類型指針綁定派生類實例,析構的時候,如果基類析構函數不是虛函數,則只會析構基類,不會析構派生類對象,從而造成內存泄漏。為什么???因為析構的時候如果沒有虛函數的動態(tài)綁定功能,就只根據指針的類型來進行的,而不是根據指針綁定的對象來進行,所以只是調用了基類的析構函數;如果基類的析構函數是虛函數,則析構的時候就要根據指針綁定的對象來調用對應的析構函數了。
構造函數為什么不能出虛函數
因為類的虛函數表指針是在構造函數中初始化的,這時候如果構造函數本身是虛函數,又應該由誰來初始化它的虛函數指針呢,所以構造函數不能是虛函數。
為什么構造函數和析構函數都不能調用虛函數
?? ?#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;
? ? }對于上述情況,基類指針指向派生類對象。構造時,先調用基類Base的構造函數,此時構函數中調用基類中的fun()函數,此時虛函數的動態(tài)綁定機制并沒有會生效,這是因為此時的派生類還不存在。析構時,先析構派生類,派生類中的fun()函數調用的是自己的fun(),然后析構基類Base,基類析構函數中的fun()調用的是基類Base自己的fun()函數,這里虛函數的動態(tài)綁定機制也沒有生效,因為此時派生類已經不存在了。
不要在構造函數中調用虛函數的原因:因為父類對象會在子類之前進行構造,此時子類部分的數據成員還未初始化, 因此調用子類的虛函數是不安全的,故而C++不會進行動態(tài)聯(lián)編。
不要在析構函數中調用虛函數的原因:析構函數是用來銷毀一個對象的,在銷毀一個對象時,先調用子類的析構函數,然后再調用基類的析構函數。所以在調用基類的析構函數時,派生類對象的數據成員已經“銷毀”,這個時再調用子類的虛函數已經沒有意義了。
c++基類的析構函數為虛函數的原因
原因
在實現多態(tài)時, 當用基類指針操作派生類, 在析構時候防止只析構基類而不析構派生類。
例子
(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;
}運行結果:
Do something in class Derived!
Output from the destructor of class Derived!
Output from the destructor of class Base!
代碼中基類的析構函數不是虛函數,在main函數中用繼承類的指針去操作繼承類的成員,釋放指針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;
?}運行結果:
Do something in class ClxBase!
Output from the destructor of class ClxBase!
代碼中基類的析構函數同樣不是虛函數,不同的是在main函數中用基類的指針去操作繼承類的成員,釋放指針P的過程是:只釋放基類的資源,而沒有調用繼承類的析構函數。 調用DoSomething()函數執(zhí)行的也是基類定義的函數。
一般情況下,這樣的刪除只能夠刪除基類對象,而不能刪除子類對象,形成了刪除一半形象,造成內存泄漏。
在公有繼承中,基類對派生類及其對象的操作,只能影響到那些從基類繼承下來的成員。如果想要用基類對非繼承成員進行操作,則要把基類的這個函數定義為虛函數。 析構函數自然也應該如此:如果它想析構子類中的重新定義或新的成員及對象,當然也應該聲明為虛的。
(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;
?}運行結果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
代碼中基類的析構函數被定義為虛函數,在main函數中用基類的指針去操作繼承類的成員,釋放指針P的過程是:釋放了繼承類的資源,再調用基類的析構函數。調用DoSomething()函數執(zhí)行的也是繼承類定義的函數。
小結:基類指針可以指向派生類的對象(多態(tài)性),如果刪除該指針delete p;就會調用該指針指向的派生類析構函數,而派生類的析構函數又自動調用基類的析構函數,這樣整個派生類的對象完全被釋放。
如果析構函數不被聲明成虛函數,則編譯器實施靜態(tài)綁定,在刪除基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。所以,將析構函數聲明為虛函數是十分必要的。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
C/C++函數參數聲明解析int?fun()?與?int?fun(void)?的區(qū)別講解
C++中int fun()和int fun(void)的區(qū)別在于函數參數的聲明方式,前者默認允許任意參數,而后者表示沒有參數,通過清晰的實例源代碼,詳細解釋了它們在函數聲明和調用中的不同之處,這篇文章介紹了C/C++函數參數聲明int?fun()與int?fun(void)的差異,需要的朋友可以參考下2024-01-01

