为什么析构函数可以能声明为虚函数,构造函数不可以
轉自:http://blog.csdn.NET/chen825919148/article/details/8020550
構造函數不能聲明為虛函數,析構函數可以聲明為虛函數,而且有時是必須聲明為虛函數。
不建議在構造函數和析構函數里面調用虛函數。
構造函數不能聲明為虛函數的原因是:
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一樣。
Note:1. 如果我們定義了一個構造函數,編譯器就不會再為我們生成默認構造函數了。
2. 編譯器生成的析構函數是非虛的,除非是一個子類,其父類有個虛析構,此時的函數虛特性來自父類。
3. 有虛函數的類,幾乎可以確定要有個虛析構函數。
4. 如果一個類不可能是基類就不要申明析構函數為虛函數,虛函數是要耗費空間的。
5. 析構函數的異常退出會導致析構不完全,從而有內存泄露。最好是提供一個管理類,在管理類中提供一個方法來析構,調用者再根據這個方法的結果決定下一步的操作。
6. 在構造函數不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到派生類中,既是采用的靜態綁定。顯然的是:當我們構造一個子類的對象時,先調用基類的構造函數,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那么寫程序,你這么寫,編譯器也不會報錯。只是你如果這么寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。
7. 在析構函數中也不要調用虛函數。在析構的時候會首先調用子類的析構函數,析構掉對象中的子類部分,然后在調用基類的析構函數析構基類部分,如果在基類的析構函數里面調用虛函數,會導致其調用已經析構了的子類對象里面的函數,這是非常危險的。
8. 記得在寫派生類的拷貝函數時,調用基類的拷貝函數拷貝基類的部分,不能忘記了。
轉自: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;
??}
運行結果:
Do something in class ClxDerived!????????????
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!??
????這段代碼中基類的析構函數不是虛函數,在main函數中用繼承類的指針去操作繼承類的成員,釋放指針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;
??}?
輸出結果:
Do something in class ClxBase!
Output from the destructor of class ClxBase!
????這段代碼中基類的析構函數同樣不是虛函數,不同的是在main函數中用基類的指針去操作繼承類的成員,釋放指針P的過程是:只是釋放了基類的資源,而沒有調用繼承類的析構函數.調用dosomething()函數執行的也是基類定義的函數.
????一般情況下,這樣的刪除只能夠刪除基類對象,而不能刪除子類對象,形成了刪除一半形象,造成內存泄漏.
????在公有繼承中,基類對派生類及其對象的操作,只能影響到那些從基類繼承下來的成員.如果想要用基類對非繼承成員進行操作,則要把基類的這個函數定義為虛函數.
????析構函數自然也應該如此:如果它想析構子類中的重新定義或新的成員及對象,當然也應該聲明為虛的.?
?
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;
??}??
運行結果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
????這段代碼中基類的析構函數被定義為虛函數,在main函數中用基類的指針去操作繼承類的成員,釋放指針P的過程是:只是釋放了繼承類的資源,再調用基類的析構函數.調用dosomething()函數執行的也是繼承類定義的函數.?
?
????如果不需要基類對派生類及對象進行操作,則不能定義虛函數,因為這樣會增加內存開銷.當類里面有定義虛函數的時候,編譯器會給類添加一個虛函數表,里面來存放虛函數指針,這樣就會增加類的存儲空間.所以,只有當一個類被用來作為基類的時候,才把析構函數寫成虛函數.
轉自:http://www.51projob.com/a/bishimianshi/2012/0414/195.html
其實這個問題最終將回答一個問題:
如果Base * pbase = new Derived;那么如果delete pbase的話,怎樣避免內存泄露?
對比1:父類的普通成員函數和虛函數均是非虛函數
來看看這時候會發生什么,具體代碼和運行結果如下:
?
其實涉及到虛函數,必然要想到虛函數表,虛函數表就是對類中的所有虛函數維持的數據結構,若是父類指針調用子類對象,這時候會查找虛函數表到實際的類型。可是如果沒有虛函數,父類指針只能“看到”自身的成員,這時候自然只能調用本身的方法(普通方法和析構方法); 因為沒有虛構函數,那么pbase指針只能調用子類對象中的父類對象部分的方法,因此只能調用父類的析構函數。對比2:父類析構函數是虛函數(將調用哪個析構函數?)
將代碼的父類的虛構函數加上virtual關鍵字,其他完全相同,得到如下代碼和運行結果:
?
由此可以看出,這個時候,就真正實現了將實際類型對象進行釋放的。同時可以得到,如果父類析構是虛函數,子類調用析構函數的話,會先調用子類的析構函數,之后會調用父類的析構函數 其實這里父類的析構函數加上了virtual,并不是說pbase釋放的時候,同時調用了子類的析構函數和父類的析構函數,它實際上指向的是子類的虛函數表,那么就是說父類指針最終只調用了子類的析構函數,由C++類本身特性,當子類析構函數調用的時候,會自動調用父類的析構函數,完成了釋放。相關小結
對于Base *pbase = new Derived;
- 如果父類函數不是析構函數,那么pbase只能“看見”父類本身的函數,這是因為沒有虛函數表讓它可以找到本身
- 如果父類析構函數是虛函數,如果delete pbase,將會先調用子類函數的析構函數,然后子類析構函數自動調用父類的析構函數,真正實現了資源釋放,防止了內存泄露
- 構造派生類的時候,會先構造基類部分,然后構造子類部分;撤銷派生類對象的時候,會先撤銷派生類部分,然后撤銷基類部分
?
總結
以上是生活随笔為你收集整理的为什么析构函数可以能声明为虚函数,构造函数不可以的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Pytorch with fastai
- 下一篇: java中ofd文件转pdf_java