本文預(yù)覽:
- 關(guān)于vptr(虛函數(shù)表指針)和vtbl(虛函數(shù)表)
- 關(guān)于this指針
- 關(guān)于Dynamic Binding(動(dòng)態(tài)綁定)
- new delete操作符重載
關(guān)于vptr(虛函數(shù)表指針)和vtbl(虛函數(shù)表)
虛函數(shù)表指針和虛函數(shù)表是C++實(shí)現(xiàn)多態(tài)的核心機(jī)制浊洞,理解vtbl和vptr的原理是理解C++對象模型的重要前提牵敷。
class里面method分為兩類:virtual 和non-virtual。非虛函數(shù)在編譯器編譯是靜態(tài)綁定的法希,所謂靜態(tài)綁定枷餐,就是編譯器直接生成JMP匯編代碼,對象在調(diào)用的時(shí)候直接跳轉(zhuǎn)到JMP匯編代碼執(zhí)行苫亦,既然是匯編代碼毛肋,那么就是不能在運(yùn)行時(shí)更改的了;虛函數(shù)的實(shí)現(xiàn)是通過虛函數(shù)表屋剑,虛函數(shù)表是一塊連續(xù)的內(nèi)存润匙,每個(gè)內(nèi)存單元中記錄一個(gè)JMP指令的地址,通過虛函數(shù)表在調(diào)用的時(shí)候才最終確定調(diào)用的是哪一個(gè)函數(shù)唉匾,這個(gè)就是動(dòng)態(tài)綁定趁桃。
class的內(nèi)部有一個(gè)virtual函數(shù),其對象的首個(gè)地址就是vptr肄鸽,指向虛函數(shù)表卫病,虛函數(shù)表是連續(xù)的內(nèi)存空間,也就是說典徘,可以通過類似數(shù)組的計(jì)算蟀苛,就可以取到多個(gè)虛函數(shù)的地址,還有一點(diǎn)逮诲,虛函數(shù)的順序和其聲明的順序是一直的帜平。
通過虛函數(shù)表來調(diào)用虛函數(shù),繞過private的限制:
typedef void(*Fun)(void);
class Base {
private:
virtual void f() {cout<<"Base::f()"<<endl;}
virtual void g() {cout<<"Base::g()"<<endl;}
virtual void h() {cout<<"Base::h()"<<endl;}
}
int main()
{
Base b;
Fun fp = nullprt;
for(int i = 0; i < 3; i++)
{
fp = (Fun)*((long*)*(long*)(&b) + i);
fp();
}
}
運(yùn)行結(jié)果:
Base::f()
Base::g()
Base::h()
需要注意的是梅鹦,由于我的電腦是64位的系統(tǒng)裆甩,vptr轉(zhuǎn)成long,八個(gè)字節(jié)齐唆,32位的int就可以了嗤栓,這個(gè)根據(jù)自己的環(huán)境去修改就可以了。&b取vptr箍邮,轉(zhuǎn)成long*,取出來是vtbl 茉帅,同樣需要轉(zhuǎn)成八字節(jié),不然在指針偏移的時(shí)候肯定就錯(cuò)了锭弊,也就是+i堪澎,虛函數(shù)地址取出來要轉(zhuǎn)換成可執(zhí)行的函數(shù)指針,這樣即使在class聲明的時(shí)候做了private限制味滞,在指針面前直接就繞過去了樱蛤。
關(guān)于this指針
上一張很久之前的圖了:
每一個(gè)成員函數(shù)都有一個(gè)默認(rèn)參數(shù)钮呀,那就是this,this代表對象本身昨凡,但是this究竟是什么呢爽醋?this就是對象的地址。
關(guān)于Dynamic Binding(動(dòng)態(tài)綁定)
怎么理解動(dòng)態(tài)綁定和靜態(tài)綁定土匀,一般來說,對于類成員函數(shù)(不論是靜態(tài)還是非靜態(tài)的成員函數(shù))都不需要?jiǎng)?chuàng)建一個(gè)在運(yùn)行時(shí)的函數(shù)表來保存形用,他們直接被編譯器編譯成匯編代碼就轧,這就是所謂的靜態(tài)綁定;所謂動(dòng)態(tài)綁定就是對象在被創(chuàng)建的時(shí)候田度,在它運(yùn)行的時(shí)候妒御,其所攜帶的虛函數(shù)表,決定了需要調(diào)用的函數(shù)镇饺,也就是說乎莉,程序在編譯完之后是不知道的,要在運(yùn)行時(shí)才能決定到底是調(diào)用哪一個(gè)函數(shù)奸笤。這就是所謂的靜態(tài)綁定和動(dòng)態(tài)綁定惋啃。
參考: C++this指針-百度百科
動(dòng)態(tài)綁定需要三個(gè)條件同時(shí)成立:
1 指針調(diào)用
2 up-cast (有向上轉(zhuǎn)型,父類指針指向子類對象)
3 調(diào)用的是虛函數(shù)
通過兩張圖看看匯編代碼:
a.vfunc1()調(diào)用虛函數(shù)监右,那么a調(diào)用的是A的虛函數(shù)边灭,還是B的虛函數(shù)?對象調(diào)用不會發(fā)生動(dòng)態(tài)綁定健盒,只有指針調(diào)用才會發(fā)生動(dòng)態(tài)綁定绒瘦。120行下面發(fā)生的call是匯編指令,call后面是一個(gè)地址扣癣,也就是函數(shù)編譯完成之后的地址了惰帽。
再看第二張:
up-cast、指針調(diào)用父虑、虛函數(shù)三個(gè)條件都滿足動(dòng)態(tài)調(diào)用该酗,call指令后面不再是靜態(tài)綁定簡單的地址,翻譯成C語言大概就是(*(p->vptr)[n](p))
士嚎,通過虛函數(shù)表來調(diào)用函數(shù)垂涯。
new delete操作符重載
舉個(gè)例子:
String* s = new String("hello world");
new 操作符主要干了兩件事
- 調(diào)用operator new分配內(nèi)存空間
- 調(diào)用構(gòu)造函數(shù)
這個(gè)在之前的文章中C++如何設(shè)計(jì)一個(gè)類2(含指針的類)將過,這里寫的就簡單一些了航邢。
這里我們要重載operator new耕赘,需要注意的是我們可以重載全局和成員操作符,兩個(gè)影響范圍是不一樣的膳殷。
void* myMalloc(size_t size)
{
void* p = malloc(size);
std::cout<<"myMalloc()"<<std::endl;
return p;
}
void myFree(void* ptr)
{
free(ptr);
std::cout<<"myFree()"<<std::endl;
}
//會覆蓋掉前面操骡,影響范圍是全局的
void* operator new(size_t size){return myMalloc(size);}
void operator delete(void* ptr) { myFree(ptr);}
class Apple{
public:
Apple(){std::cout<<"Apple::Apple()"<<std::endl;}
void* operator new(size_t size){std::cout<<"+++++++"<<std::endl; return myMalloc(size);}
void operator delete(void* ptr) {std::cout<<"-------"<<std::endl;return myFree(ptr);}
};
int main(int argc, const char * argv[]) {
//調(diào)用class重載的
Apple* apple = new Apple;
delete apple;
//強(qiáng)制調(diào)用全局的
Apple* app = ::new Apple;
::delete app;
return 0;
}
new[] 和delete[]是一個(gè)道理九火。