virtual 析構函數的作用
虛析構函數使得在刪除指向子類對象的基類指針時可以調用子類的析構函數達到釋放子類中堆內存的目的,而防止內存泄露的.
調用時機
在談論實現之前先明確一下析構函數的調用時機
- 局部變量在作用域結束的時候,程序會默認調用析構函數(如果有的話)
- 主動調用delete(delete [])方法, 首先會查看對象類型,確定是否需要調用虛構函數. 如果發現對象的析構函數是virtual 的,就會使用virtual調用機制.
對象佈局
要想了解virtual調用機制,首先就要清楚對象佈局
如果派生類增加成員,則簡單的放在基類的成員後面
vptr 虛指針: 指向對象的虛函數表
vtbl 虛函數表 : 存儲對象虛函數地址
派生類通過vptr 找到vtbl,然後找到對應的虛函數進行調用
覆蓋(overriding)
定義一個和基類中虛函數名字和類型都相同的函數,以使派生類的函數代替基類中的版本被放入vtal的技術成爲 覆蓋(overriding)
virtual 函數調用機制
本質 : 虛函數調用產生的目標代碼首先簡單的尋址vptr ,通過它找到對應的vtbl,然後調用其中正確的函數. 其代價大約調用了兩次內存訪問加上一次普通的函數調用.
Demo 實踐檢驗真理
#include <iostream>
using namespace std;
class B {
public:
virtual void f() const { cout << "B::f1()\n"; }
virtual void f2() const { cout << "B::f2()\n"; }
virtual void f3() const { cout << "B::f3()\n"; }
virtual void f4() const { cout << "B::f4()\n"; }
void g(){ cout << "B::g()\n"; }
};
struct D : public B{
public:
void f() const override{ cout << "D::f1()\n"; }
void f4() const override{ cout << "D::f4()\n"; }
void g(){ cout << "D::g()\n"; }
};
struct DD : public D {
public:
void f() { cout << "DD::f1()\n"; }
void g() { cout << "DD::g()\n"; }
};
typedef void (*func)();
void call(B& b ) {
for(int i =0 ; i < 4 ; i++) {
unsigned long* vtbl = (unsigned long*)(*(unsigned long*)&base) + i;
cout << "slot address: " << vtbl << endl;
cout << "func address: " << *vtbl << endl;
func pfunc = (func)*(vtbl);
pfunc();
}
b.g();
cout << "++++++++++++++++++++++++++++++++++++++++++" << endl;
}
int main(int argc, const char * argv[]) {
B b;
D d;
DD dd;
call(b);
call(d);
call(dd);
b.f();
b.g();
d.f();
d.g();
dd.f();
dd.g();
return 0;
}
結果:
slot address: 0x1000040e0
func address:
B::f1()
slot address: 0x1000040e8
func address:
B::f2()
slot address: 0x1000040f0
func address:
B::f3()
slot address: 0x1000040f8
func address:
B::f4()
B::g()
++++++++++++++++++++++++++++++++++++++++++
slot address: 0x100004120
func address:
D::f1()
slot address: 0x100004128
func address:
B::f2()
slot address: 0x100004130
func address:
B::f3()
slot address: 0x100004138
func address:
D::f4()
B::g()
++++++++++++++++++++++++++++++++++++++++++
slot address: 0x100004168
func address:
D::f1()
slot address: 0x100004170
func address:
B::f2()
slot address: 0x100004178
func address:
B::f3()
slot address: 0x100004180
func address:
D::f4()
B::g()
++++++++++++++++++++++++++++++++++++++++++
B::f1()
B::g()
D::f1()
D::g()
DD::f1()
DD::g()
Program ended with exit code: 0
代碼分析
對象b
通過虛函數表分別調用了自己的f f1 f2 f3
對應的函數地址:
f :
f2:
f3:
f4:
對象d
通過虛函數表分別調用了 父類的f2 f3
調用了覆蓋函數f f4
對應函數地址:
f:
f2:
f3:
f4:
對象dd
通過虛函數表分別調用了 父類的父級的f2 f3
調用了父級的函數f f4
對應函數地址:
f:
f2:
f3:
f4:
virtual 是如何實現的呢?
通過上面的理解和具體的demo結果可知,虛函數的實現是如何的,即通過虛指針和虛函數表來實現具體函數調用
虛析構函數的作用呢?
和剛剛開始說明的作用一樣,就是爲了解決這種父類指針指向子類對象時,對象銷燬時調用子類的析構函數銷燬數據避免內存泄露的產生