文章目錄
- 建構函式不能聲明為虛函數,解構函式可以聲明為虛函數,而且有時是必須聲明為虛函數。
建構函式不能聲明為虛函數,解構函式可以聲明為虛函數,而且有時是必須聲明為虛函數。
不建議在建構函式和解構函式裡面調用虛函數。
建構函式不能聲明為虛函數的原因是:
解釋一:所謂虛函數就是多態情況下只執行一個。而從繼承的概念來講,總是要先構造父類對象,然後才能是子類對象。如果建構函式設為虛函數,那麼當你在構造父類的建構函式時就不得不顯示的調用構造。還有一個原因就是為了防錯,試想如果你在子類中一不小心重寫了個跟父類建構函式一樣的函數,那麼你的父類的建構函式將被覆蓋,也即不能完成父類的構造.就會出錯。
解釋二:虛函數的主要意義在於被衍生類別繼承從而產生多態。衍生類別的建構函式中,編譯器會加入構造基類的代碼,如果基類的建構函式用到參數,則衍生類別在其建構函式的初始化列表中必須為基類給出參數,就是這個原因。
虛函數的意思就是開啟動態綁定,程式會根據對象的動態類型來選擇要調用的方法。然而在建構函式啟動並執行時候,這個對象的動態類型還不完整,沒有辦法確定它到底是什麼類型,故建構函式不能動態綁定。(動態綁定是根據對象的動態類型而不是函數名,在調用建構函式之前,這個對象根本就不存在,它怎麼動態綁定?)
編譯器在調用基類的建構函式的時候並不知道你要構造的是一個基類的對象還是一個衍生類別的對象。
解構函式設為虛函數的作用:
解釋:在類的繼承中,如果有基類指標指向衍生類別,那麼用基類指標delete時,如果不定義成虛函數,衍生類別中派生的那部分無法析構。
例:
#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;
}
輸出結果為:Delete class B
Delete class A
如果把A的virtual去掉:那就變成了Delete class A
因此在類的繼承體系中,基類的解構函式不聲明為虛函數容易造成記憶體流失。所以如果你設計一定類可能是基類的話,必須要聲明其為虛函數。正如Symbian中的CBase一樣。Note:
1. 如果我們定義了一個建構函式,編譯器就不會再為我們產生預設建構函式了。
2. 編譯器產生的解構函式是非虛的,除非是一個子類,其父類有個虛析構,此時的函數虛特性來自父類。
3. 有虛函數的類,幾乎可以確定要有個虛解構函式。
4. 如果一個類不可能是基類就不要申明解構函式為虛函數,虛函數是要耗費空間的。
5. 解構函式的異常退出會導致析構不完全,從而有記憶體泄露的問題。最好是提供一個管理類,在管理類中提供一個方法來析構,調用者再根據這個方法的結果決定下一步的操作。
6. 在建構函式不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到衍生類別中,既是採用的靜態繫結。顯然的是:當我們構造一個子類的對象時,先調用基類的建構函式,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那麼寫程式,你這麼寫,編譯器也不會報錯。只是你如果這麼寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。
7. 在解構函式中也不要調用虛函數。在析構的時候會首先調用子類的解構函式,析構掉對象中的子類部分,然後在調用基類的解構函式析構基類部分,如果在基類的解構函式裡面調用虛函數,會導致其調用已經析構了的子類對象裡面的函數,這是非常危險的。
8. 記得在寫衍生類別的拷貝函數時,調用基類的拷貝函數拷貝基類的部分,不能忘記了。
如果一個類是作為基類使用,那麼他的虛構函數一定要是虛的,即用virtual關鍵字(參數為零則為純虛函數).
否則會有記憶體流失(很重要),因為當用基類的指標刪除一個衍生類別的對象時,要調用衍生類別的解構函式.但是
其子類或者子子類可以的解構函式可以是虛函數,也可以不是虛函數.(不加virtual 關鍵字則不會調用派生
類的解構函式,而上面用了ClxBase *pTest = new ClxDerived;語句也就是new的ClxDerived對象沒有 銷毀,所以產生記憶體流失)
2.類中的虛函數,如果一個類中的函數被聲明成為虛函數,那麼其子類不用在聲明為虛函數(當子類還有子類時),
也可以聲明為虛函數.結果是一樣的.同虛解構函式的道理是一樣的.當然,並不是要把所有類的解構函式都寫
成虛函數。因為當類裡面有虛函數的時候,編譯器會給類添加一個虛函數表,裡面來存放虛函數指標,這樣就
會增加類的儲存空間。所以,只有當一個類被用來作為基類的時候,才把解構函式寫成虛函數。
具體例子:
#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 用基類的指標刪除一個衍生類別的對象時
ClxDerived *pTest3 = new ClxDerived;
pTest3->DoSomething();
delete pTest3;//3
ClxBase *pTest4 = new ClxThrived;
pTest4->DoSomething();
delete pTest4;//4 用基類的指標刪除一個衍生類別的對象時
ClxDerived *pTest5 = new ClxThrived;
pTest5->DoSomething();
delete pTest5;//5 用基類的指標刪除一個衍生類別的對象時
ClxThrived *pTest6 = new ClxThrived;
pTest6->DoSomething();
delete pTest6;//6
}
不建議在建構函式和解構函式裡面調用虛函數。
建構函式不能聲明為虛函數的原因是:
解釋一:所謂虛函數就是多態情況下只執行一個。而從繼承的概念來講,總是要先構造父類對象,然後才能是子類對象。如果建構函式設為虛函數,那麼當你在構造父類的建構函式時就不得不顯示的調用構造。還有一個原因就是為了防錯,試想如果你在子類中一不小心重寫了個跟父類建構函式一樣的函數,那麼你的父類的建構函式將被覆蓋,也即不能完成父類的構造.就會出錯。
解釋二:虛函數的主要意義在於被衍生類別繼承從而產生多態。衍生類別的建構函式中,編譯器會加入構造基類的代碼,如果基類的建構函式用到參數,則衍生類別在其建構函式的初始化列表中必須為基類給出參數,就是這個原因。
虛函數的意思就是開啟動態綁定,程式會根據對象的動態類型來選擇要調用的方法。然而在建構函式啟動並執行時候,這個對象的動態類型還不完整,沒有辦法確定它到底是什麼類型,故建構函式不能動態綁定。(動態綁定是根據對象的動態類型而不是函數名,在調用建構函式之前,這個對象根本就不存在,它怎麼動態綁定?)
編譯器在調用基類的建構函式的時候並不知道你要構造的是一個基類的對象還是一個衍生類別的對象。
解構函式設為虛函數的作用:
解釋:在類的繼承中,如果有基類指標指向衍生類別,那麼用基類指標delete時,如果不定義成虛函數,衍生類別中派生的那部分無法析構。
例:
#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;
}
輸出結果為:Delete class B
Delete class A
如果把A的virtual去掉:那就變成了Delete class A
因此在類的繼承體系中,基類的解構函式不聲明為虛函數容易造成記憶體流失。所以如果你設計一定類可能是基類的話,必須要聲明其為虛函數。正如Symbian中的CBase一樣。Note:
1. 如果我們定義了一個建構函式,編譯器就不會再為我們產生預設建構函式了。
2. 編譯器產生的解構函式是非虛的,除非是一個子類,其父類有個虛析構,此時的函數虛特性來自父類。
3. 有虛函數的類,幾乎可以確定要有個虛解構函式。
4. 如果一個類不可能是基類就不要申明解構函式為虛函數,虛函數是要耗費空間的。
5. 解構函式的異常退出會導致析構不完全,從而有記憶體泄露的問題。最好是提供一個管理類,在管理類中提供一個方法來析構,調用者再根據這個方法的結果決定下一步的操作。
6. 在建構函式不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到衍生類別中,既是採用的靜態繫結。顯然的是:當我們構造一個子類的對象時,先調用基類的建構函式,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那麼寫程式,你這麼寫,編譯器也不會報錯。只是你如果這麼寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。
7. 在解構函式中也不要調用虛函數。在析構的時候會首先調用子類的解構函式,析構掉對象中的子類部分,然後在調用基類的解構函式析構基類部分,如果在基類的解構函式裡面調用虛函數,會導致其調用已經析構了的子類對象裡面的函數,這是非常危險的。
8. 記得在寫衍生類別的拷貝函數時,調用基類的拷貝函數拷貝基類的部分,不能忘記了。
如果一個類是作為基類使用,那麼他的虛構函數一定要是虛的,即用virtual關鍵字(參數為零則為純虛函數).
否則會有記憶體流失(很重要),因為當用基類的指標刪除一個衍生類別的對象時,要調用衍生類別的解構函式.但是
其子類或者子子類可以的解構函式可以是虛函數,也可以不是虛函數.(不加virtual 關鍵字則不會調用派生
類的解構函式,而上面用了ClxBase *pTest = new ClxDerived;語句也就是new的ClxDerived對象沒有 銷毀,所以產生記憶體流失)
2.類中的虛函數,如果一個類中的函數被聲明成為虛函數,那麼其子類不用在聲明為虛函數(當子類還有子類時),
也可以聲明為虛函數.結果是一樣的.同虛解構函式的道理是一樣的.當然,並不是要把所有類的解構函式都寫
成虛函數。因為當類裡面有虛函數的時候,編譯器會給類添加一個虛函數表,裡面來存放虛函數指標,這樣就
會增加類的儲存空間。所以,只有當一個類被用來作為基類的時候,才把解構函式寫成虛函數。
具體例子:
#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 用基類的指標刪除一個衍生類別的對象時
ClxDerived *pTest3 = new ClxDerived;
pTest3->DoSomething();
delete pTest3;//3
ClxBase *pTest4 = new ClxThrived;
pTest4->DoSomething();
delete pTest4;//4 用基類的指標刪除一個衍生類別的對象時
ClxDerived *pTest5 = new ClxThrived;
pTest5->DoSomething();
delete pTest5;//5 用基類的指標刪除一個衍生類別的對象時
ClxThrived *pTest6 = new ClxThrived;
pTest6->DoSomething();
delete pTest6;//6
}