C++学习笔记-----不要在构造函数和析构函数中调用虚函数
考慮下面的程序:
#include <iostream> using namespace std;class Base { public:Base() { cout << "Base Construct" << endl; func(); }~Base() { cout << "Base Destroy" << endl; }void toG() { g(); }virtual void func() { cout << "Base virtual function" << endl; } private:virtual void g() { cout << "Base Private virtual" << endl; } };class Derive : public Base { public:Derive() : Base() { cout << "Derive Construct" << endl; func(); }~Derive() { cout << "Derive Destroy" << endl; }void func() { cout << "Derive virtual function" << endl; } private:void g() { cout << "Derive Private virtual" << endl; } };int main() {Derive d;cout << endl;d.toG();cout << endl;cin.get();return 0; }如你所見,我們定義了一個基類Base,又定義了一個派生類Derive并以public的形式繼承它。
在構造函數中分別輸出不同的字符串常量然后調用虛函數func()。
另外在基類中又存在一個toG()函數用來調用函數內部的虛函數g(),通常這里的g函數應該定義在私有成員作用域中,為了實現用同一個基類接口調用不用的派生類虛函數的目的。
讓我們來看一下程序的運行結果:
發現什么不對的地方了嗎,如果沒有,我們一步步分析:
Derive d;首先像往常一樣,定義了一個派生類的實例化對象,構造的順序是從基類到派生類。先調用基類的構造函數輸出“Base Construct”,又調用了基類的func()函數輸出了“Base virtual function“,到這里基類部分構造完成,開始進入派生類的構造階段,輸出”Derive Construct“,又調用了派生類的func()函數輸出了”Derive virtual function"。 d.toG();我們用派生類的對象調用基類的接口函數toG(),在函數內部調用了派生類的g()函數輸出”Derive Private virtual“。比較上面的結果,不難發現,同樣是在基類的作用域下,func()函數調用的是基類的,g()函數卻調用的是派生類的。
這正是問題所在,想一想,上述哪種調用結果是符合預期的,為什么會出現這樣的不一致?
涉及到虛函數調用的問題基本上都可以用虛函數表來分析解決,在定義派生類對象的時候,因為func和g兩個函數都是虛函數且在派生類中都重新定義了,所以虛函數表中函數的地址都會被覆蓋成派生類的虛函數的地址,也就是說只要我們用派生類的對象調用這兩個函數,會在虛函數表中尋找最佳匹配的函數獲得它的函數地址并調用它,不管作用域是在基類還是在派生類,都會調用派生類的相應函數。
然而func()的調用好像并非如此,這就是為什么說不要在構造函數中調用虛函數的原因。
至于為什么會出現這樣的結果,我們知道,在派生類對象的構造階段是先構造其基類部分的,也就是可以理解成在構造基類的時候派生類的部分還不存在,試想,我們又怎么能去調用一個并不存在的東西的子成員呢?編譯器也正是假設在構造階段派生類對象還不存在,所以不會去調用派生類的虛函數。
歸結一點,不要在構造函數中調用虛函數,更準確的說法是不要在基類的構造函數中調用虛函數,因為編譯器調用的永遠都是屬于基類的那個虛函數而不是派生類的。
同樣的原因可以解釋析構函數,因為析構函數的調用時從派生類到基類的,也就是說先銷毀派生類獨立的那部分,在向上銷毀它的基類。那么如果在基類的析構函數中調用了虛函數,此時派生類部分已經被銷毀了,又不存在了,那調用的虛函數永遠都是基類的那個。
總結
以上是生活随笔為你收集整理的C++学习笔记-----不要在构造函数和析构函数中调用虚函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++学习笔记-----operator
- 下一篇: C++学习笔记-----在重载的赋值运算