本文預(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++對(duì)象模型的重要前提。
class里面method分為兩類:virtual 和non-virtual。非虛函數(shù)在編譯器編譯是靜態(tài)綁定的,所謂靜態(tài)綁定,就是編譯器直接生成JMP匯編代碼,對(duì)象在調(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ù),其對(duì)象的首個(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代表對(duì)象本身,但是this究竟是什么呢?this就是對(duì)象的地址。
關(guān)于Dynamic Binding(動(dòng)態(tài)綁定)
怎么理解動(dòng)態(tài)綁定和靜態(tài)綁定,一般來說,對(duì)于類成員函數(shù)(不論是靜態(tài)還是非靜態(tài)的成員函數(shù))都不需要?jiǎng)?chuàng)建一個(gè)在運(yùn)行時(shí)的函數(shù)表來保存,他們直接被編譯器編譯成匯編代碼,這就是所謂的靜態(tài)綁定;所謂動(dòng)態(tài)綁定就是對(duì)象在被創(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)型,父類指針指向子類對(duì)象)
3 調(diào)用的是虛函數(shù)
通過兩張圖看看匯編代碼:

a.vfunc1()調(diào)用虛函數(shù),那么a調(diào)用的是A的虛函數(shù),還是B的虛函數(shù)?對(duì)象調(diào)用不會(huì)發(fā)生動(dòng)態(tài)綁定,只有指針調(diào)用才會(huì)發(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)綁定簡(jiǎn)單的地址,翻譯成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(含指針的類)將過,這里寫的就簡(jiǎn)單一些了。
這里我們要重載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;
}
//會(huì)覆蓋掉前面,影響范圍是全局的
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è)道理。