为什么析构函数可以能声明为虚函数,构造函数不可以
轉(zhuǎn)自:http://blog.csdn.NET/chen825919148/article/details/8020550
構(gòu)造函數(shù)不能聲明為虛函數(shù),析構(gòu)函數(shù)可以聲明為虛函數(shù),而且有時是必須聲明為虛函數(shù)。
不建議在構(gòu)造函數(shù)和析構(gòu)函數(shù)里面調(diào)用虛函數(shù)。
構(gòu)造函數(shù)不能聲明為虛函數(shù)的原因是:
1 構(gòu)造一個對象的時候,必須知道對象的實際類型,而虛函數(shù)行為是在運行期間確定實際類型的。而在構(gòu)造一個對象時,由于對象還未構(gòu)造成功。編譯器無法知道對象 的實際類型,是該類本身,還是該類的一個派生類,或是更深層次的派生類。無法確定。。。
2 虛函數(shù)的執(zhí)行依賴于虛函數(shù)表。而虛函數(shù)表在構(gòu)造函數(shù)中進行初始化工作,即初始化vptr,讓他指向正確的虛函數(shù)表。而在構(gòu)造對象期間,虛函數(shù)表還沒有被初 始化,將無法進行。
虛函數(shù)的意思就是開啟動態(tài)綁定,程序會根據(jù)對象的動態(tài)類型來選擇要調(diào)用的方法。然而在構(gòu)造函數(shù)運行的時候,這個對象的動態(tài)類型還不完整,沒有辦法確定它到底是什么類型,故構(gòu)造函數(shù)不能動態(tài)綁定。(動態(tài)綁定是根據(jù)對象的動態(tài)類型而不是函數(shù)名,在調(diào)用構(gòu)造函數(shù)之前,這個對象根本就不存在,它怎么動態(tài)綁定?)編譯器在調(diào)用基類的構(gòu)造函數(shù)的時候并不知道你要構(gòu)造的是一個基類的對象還是一個派生類的對象。
析構(gòu)函數(shù)設(shè)為虛函數(shù)的作用:
解釋:在類的繼承中,如果有基類指針指向派生類,那么用基類指針delete時,如果不定義成虛函數(shù),派生類中派生的那部分無法析構(gòu)。
例:
#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;
}
輸出結(jié)果為:Delete class B
Delete class A
如果把A的virtual去掉:那就變成了Delete class A也就是說不會刪除派生類里的剩余部分內(nèi)容,也即不調(diào)用派生類的虛函數(shù)
因此在類的繼承體系中,基類的析構(gòu)函數(shù)不聲明為虛函數(shù)容易造成內(nèi)存泄漏。所以如果你設(shè)計一定類可能是基類的話,必須要聲明其為虛函數(shù)。正如Symbian中的CBase一樣。
Note:1. 如果我們定義了一個構(gòu)造函數(shù),編譯器就不會再為我們生成默認構(gòu)造函數(shù)了。
2. 編譯器生成的析構(gòu)函數(shù)是非虛的,除非是一個子類,其父類有個虛析構(gòu),此時的函數(shù)虛特性來自父類。
3. 有虛函數(shù)的類,幾乎可以確定要有個虛析構(gòu)函數(shù)。
4. 如果一個類不可能是基類就不要申明析構(gòu)函數(shù)為虛函數(shù),虛函數(shù)是要耗費空間的。
5. 析構(gòu)函數(shù)的異常退出會導(dǎo)致析構(gòu)不完全,從而有內(nèi)存泄露。最好是提供一個管理類,在管理類中提供一個方法來析構(gòu),調(diào)用者再根據(jù)這個方法的結(jié)果決定下一步的操作。
6. 在構(gòu)造函數(shù)不要調(diào)用虛函數(shù)。在基類構(gòu)造的時候,虛函數(shù)是非虛,不會走到派生類中,既是采用的靜態(tài)綁定。顯然的是:當(dāng)我們構(gòu)造一個子類的對象時,先調(diào)用基類的構(gòu)造函數(shù),構(gòu)造子類中基類部分,子類還沒有構(gòu)造,還沒有初始化,如果在基類的構(gòu)造中調(diào)用虛函數(shù),如果可以的話就是調(diào)用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構(gòu)造父類對象部分的時候調(diào)用子類的虛函數(shù)實現(xiàn)。但是不是說你不可以那么寫程序,你這么寫,編譯器也不會報錯。只是你如果這么寫的話編譯器不會給你調(diào)用子類的實現(xiàn),而是還是調(diào)用基類的實現(xiàn)。
7. 在析構(gòu)函數(shù)中也不要調(diào)用虛函數(shù)。在析構(gòu)的時候會首先調(diào)用子類的析構(gòu)函數(shù),析構(gòu)掉對象中的子類部分,然后在調(diào)用基類的析構(gòu)函數(shù)析構(gòu)基類部分,如果在基類的析構(gòu)函數(shù)里面調(diào)用虛函數(shù),會導(dǎo)致其調(diào)用已經(jīng)析構(gòu)了的子類對象里面的函數(shù),這是非常危險的。
8. 記得在寫派生類的拷貝函數(shù)時,調(diào)用基類的拷貝函數(shù)拷貝基類的部分,不能忘記了。
轉(zhuǎn)自:http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html
1.第一段代碼
#include<iostream>
using namespace std;
class ClxBase{
public:
????ClxBase() {};
????~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
????void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
????ClxDerived() {};
????~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
????void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
??int???main(){??
??ClxDerived *p =??new ClxDerived;
??p->DoSomething();
??delete p;
??return 0;
??}
運行結(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的過程是:先釋放繼承類的資源,再釋放基類資源.?
?
2.第二段代碼
#include<iostream>
using namespace std;
class ClxBase{
public:
????ClxBase() {};
????~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
????void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
????ClxDerived() {};
????~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
????void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }
};
??int???main(){??
??ClxBase *p =??new ClxDerived;
??p->DoSomething();
??delete p;
??return 0;
??}?
輸出結(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)存泄漏.
????在公有繼承中,基類對派生類及其對象的操作,只能影響到那些從基類繼承下來的成員.如果想要用基類對非繼承成員進行操作,則要把基類的這個函數(shù)定義為虛函數(shù).
????析構(gòu)函數(shù)自然也應(yīng)該如此:如果它想析構(gòu)子類中的重新定義或新的成員及對象,當(dāng)然也應(yīng)該聲明為虛的.?
?
3.第三段代碼:
#include<iostream>
using namespace std;
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() {};
????~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
????void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
??int???main(){??
??ClxBase *p =??new ClxDerived;
??p->DoSomething();
??delete p;
??return 0;
??}??
運行結(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ù).?
?
????如果不需要基類對派生類及對象進行操作,則不能定義虛函數(shù),因為這樣會增加內(nèi)存開銷.當(dāng)類里面有定義虛函數(shù)的時候,編譯器會給類添加一個虛函數(shù)表,里面來存放虛函數(shù)指針,這樣就會增加類的存儲空間.所以,只有當(dāng)一個類被用來作為基類的時候,才把析構(gòu)函數(shù)寫成虛函數(shù).
轉(zhuǎn)自:http://www.51projob.com/a/bishimianshi/2012/0414/195.html
其實這個問題最終將回答一個問題:
如果Base * pbase = new Derived;那么如果delete pbase的話,怎樣避免內(nèi)存泄露?
對比1:父類的普通成員函數(shù)和虛函數(shù)均是非虛函數(shù)
來看看這時候會發(fā)生什么,具體代碼和運行結(jié)果如下:
?
其實涉及到虛函數(shù),必然要想到虛函數(shù)表,虛函數(shù)表就是對類中的所有虛函數(shù)維持的數(shù)據(jù)結(jié)構(gòu),若是父類指針調(diào)用子類對象,這時候會查找虛函數(shù)表到實際的類型??墒侨绻麤]有虛函數(shù),父類指針只能“看到”自身的成員,這時候自然只能調(diào)用本身的方法(普通方法和析構(gòu)方法); 因為沒有虛構(gòu)函數(shù),那么pbase指針只能調(diào)用子類對象中的父類對象部分的方法,因此只能調(diào)用父類的析構(gòu)函數(shù)。對比2:父類析構(gòu)函數(shù)是虛函數(shù)(將調(diào)用哪個析構(gòu)函數(shù)?)
將代碼的父類的虛構(gòu)函數(shù)加上virtual關(guān)鍵字,其他完全相同,得到如下代碼和運行結(jié)果:
?
由此可以看出,這個時候,就真正實現(xiàn)了將實際類型對象進行釋放的。同時可以得到,如果父類析構(gòu)是虛函數(shù),子類調(diào)用析構(gòu)函數(shù)的話,會先調(diào)用子類的析構(gòu)函數(shù),之后會調(diào)用父類的析構(gòu)函數(shù) 其實這里父類的析構(gòu)函數(shù)加上了virtual,并不是說pbase釋放的時候,同時調(diào)用了子類的析構(gòu)函數(shù)和父類的析構(gòu)函數(shù),它實際上指向的是子類的虛函數(shù)表,那么就是說父類指針最終只調(diào)用了子類的析構(gòu)函數(shù),由C++類本身特性,當(dāng)子類析構(gòu)函數(shù)調(diào)用的時候,會自動調(diào)用父類的析構(gòu)函數(shù),完成了釋放。相關(guān)小結(jié)
對于Base *pbase = new Derived;
- 如果父類函數(shù)不是析構(gòu)函數(shù),那么pbase只能“看見”父類本身的函數(shù),這是因為沒有虛函數(shù)表讓它可以找到本身
- 如果父類析構(gòu)函數(shù)是虛函數(shù),如果delete pbase,將會先調(diào)用子類函數(shù)的析構(gòu)函數(shù),然后子類析構(gòu)函數(shù)自動調(diào)用父類的析構(gòu)函數(shù),真正實現(xiàn)了資源釋放,防止了內(nèi)存泄露
- 構(gòu)造派生類的時候,會先構(gòu)造基類部分,然后構(gòu)造子類部分;撤銷派生類對象的時候,會先撤銷派生類部分,然后撤銷基類部分
?
總結(jié)
以上是生活随笔為你收集整理的为什么析构函数可以能声明为虚函数,构造函数不可以的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Pytorch with fastai
- 下一篇: java中ofd文件转pdf_java