一、虛表僻造、虛指針、動(dòng)態(tài)綁定
這一部分孩饼,我們介紹下 繼承體系下髓削,類和對(duì)象的存儲(chǔ)形式。
1.1 vptr 虛指針 和 vtable 虛表
對(duì)于虛指針和虛表的定義镀娶,這里引用一段quora上的一個(gè)回復(fù)(這里我已經(jīng)翻譯成中文): 如果一個(gè)類存在一個(gè)或多個(gè)虛函數(shù)立膛,編譯器會(huì)為這個(gè)類的實(shí)例 (對(duì)象) 創(chuàng)建一個(gè)隱藏的成員變量,即虛指針(virtual-pointer)梯码,簡(jiǎn)稱 vptr旧巾。 vptr 指向一個(gè)包含一組函數(shù)指針的表,我們稱之為 虛表 (virtual table)忍些,簡(jiǎn)稱 vtable。虛表由編譯器創(chuàng)建坎怪,虛表中的每一項(xiàng)均是 一個(gè)指向?qū)?yīng)虛函數(shù)的指針罢坝。
為了實(shí)現(xiàn)動(dòng)態(tài)綁定 (dynamic binding),編譯器為每一個(gè)擁有虛函數(shù)的類 (和它的子類) 創(chuàng)建一張?zhí)摫斫亮>幾g器將虛函數(shù)的地址存放到對(duì)應(yīng) 類的虛表中嘁酿。 當(dāng)通過(guò)基類指針 (或父類指針,Base * pb) 調(diào)用虛函數(shù)時(shí)男应,編譯器插入一段在虛表中查找虛函數(shù)地址和獲取 vptr 的代碼闹司。 所以才能夠調(diào)用到”正確”的函數(shù),實(shí)現(xiàn)動(dòng)態(tài)綁定沐飘。
關(guān)于 vptr 和 vtable 的調(diào)用游桩,這里用侯捷老師 PPT 上的一張圖表示:
關(guān)于 類 A牲迫、B、C 的結(jié)構(gòu)聲明參考下面的代碼 (注意這里不包含具體實(shí)現(xiàn)):
// 上圖中 類 A借卧、B盹憎、C 的聲明classA{public:virtualvoidvfunc1();virtualvoidvfunc2();voidfunc1();voidfunc2();private:intm_data1,m_data2;}classB:publicA{public:virtualvoidvfunc1();voidfunc2();private:intm_data3;}classC:publicB{public:virtualvoidvfunc1();voidfunc2();private:intm_data1,m_data4;}
1.2 this pointer (template method)
在繼承體系中,子類對(duì)象調(diào)用一個(gè)方法時(shí)铐刘,如果該類本身這個(gè)函數(shù)陪每,那么會(huì)調(diào)用這個(gè)函數(shù);如果本身沒(méi)有镰吵,那么編譯器會(huì)沿著繼承樹向上查找父類中是否有該方法檩禾。
侯捷老師PPT中的一張圖很好地體現(xiàn)了這種調(diào)用機(jī)制:
1.3 dynamic binding 動(dòng)態(tài)綁定
1.3.1 什么是動(dòng)態(tài)綁定?
動(dòng)態(tài)綁定是編程語(yǔ)言的一種特性(或機(jī)制)疤祭,它允許程序在運(yùn)行時(shí)決定執(zhí)行操作的細(xì)節(jié)盼产,而不是在編譯時(shí)就確定。在設(shè)計(jì)一個(gè)軟件時(shí)画株,通常會(huì)出現(xiàn)下面兩類情況:
類的接口已經(jīng)確定辆飘,但是還不知道具體怎么實(shí)現(xiàn)
開發(fā)者知道需要什么算法,但是不知道具體的操作
這兩種情況下谓传,開發(fā)者都需要延遲決定蜈项,延遲到什么時(shí)候呢?延遲到已經(jīng)有足夠的信息去做一個(gè)正確的決策续挟。此時(shí)如果能不修改原先的實(shí)現(xiàn)紧卒,我們的目標(biāo)就達(dá)到了波俄。
動(dòng)態(tài)綁定正是為了滿足這些需求而存在涮拗,結(jié)果就是更靈活和可伸縮的軟件架構(gòu)。比如在軟件開發(fā)初期帮非,不需要做出所有設(shè)計(jì)決策直颅。這里我們討論下靈活性和可伸縮性:
flexibility (靈活性): 很容易將現(xiàn)存組件和新的配置合并到一起
extensibility (擴(kuò)展性): 很容易添加新組件
C++ 通過(guò) 虛表和虛指針機(jī)制 實(shí)現(xiàn)對(duì)動(dòng)態(tài)綁定的支持博个,具體的機(jī)制我們?cè)谏厦嬉呀?jīng)談到,這里不再贅述功偿。
1.3.2 動(dòng)態(tài)綁定在 C++ 中的體現(xiàn)
在 C++ 中盆佣,動(dòng)態(tài)綁定的標(biāo)志是在聲明類方法時(shí),在方法名前面顯式地添加 virtual 關(guān)鍵字械荷。比如下面這樣:
classBase{public:virtualvoidvfunc1(){std::cout<<"Base::vfunc1()"<
只有類的成員函數(shù)才能被聲明為虛函數(shù)共耍,下面三種是不可以的:
普通的函數(shù) (不屬于任何一個(gè)類)
類的成員變量
靜態(tài)方法 (static 修飾的成員函數(shù))
virtual 修飾的成員函數(shù)的接口是固定的,但是子類中的同名成員函數(shù)可以修改默認(rèn)實(shí)現(xiàn)吨瞎,比如像下面這樣:
classDerived_1{public:virtualvoidvfunc1(){std::cout<<"Derived_1::vfunc1() "<
注意:上面的代碼中痹兜, virtual 是可選的,即便不寫颤诀,它仍然是虛函數(shù)字旭!
在程序運(yùn)行時(shí)对湃,虛函數(shù)調(diào)度機(jī)制會(huì)根據(jù)對(duì)象的”動(dòng)態(tài)類型”選擇對(duì)應(yīng)的成員函數(shù)。 被選擇的成員函數(shù)依賴于被指針指向的對(duì)象谐算,而不是指針的類型熟尉。看下面代碼:
voidfoo(Base*bp){bp->vf1();/* virtual */}Baseb;Base*bp=&b;bp->vf1();// 打印 "Base::vfunc1()"Derived_1d;bp=&d;bp->vf1();// 打印 "Derived_1::vfunc1()"foo(&b);// 打印 "Base::vfunc1()"foo(&d);// 打印 "Derived_1::vfunc1()"洲脂,這里存在一個(gè)隱式的向上轉(zhuǎn)型
關(guān)于動(dòng)態(tài)綁定斤儿,更多細(xì)節(jié)參考C++ dynamic binding。
Part 2: const 補(bǔ)充
這個(gè)小結(jié)中恐锦,關(guān)于 const 的所有例子均來(lái)自于msdn往果。為了便于理解, 對(duì)代碼進(jìn)行了稍微的調(diào)整一铅。
2.1 const 修飾指針
下面這個(gè)例子中陕贮, const 修飾的是指針,因此不能修改指針 aptr 的值潘飘,即 aptr 不能指向另一個(gè)位置肮之。
// constant_values3.cppintmain(){char*mybuf=0,*yourbuf;char*constaptr=mybuf;*aptr='a';// OKaptr=yourbuf;// C3892}
2.2 const 修飾指針指向的數(shù)據(jù)
下面這個(gè)例子中, const 修飾的是指針指向的數(shù)據(jù)卜录,因此可以修改指針的值戈擒,但是不能修改指針指向的數(shù)據(jù)。
// constant_values4.cpp#include intmain(){constchar*mybuf="test";char*yourbuf="test2";printf_s("%s\n",mybuf);constchar*bptr=mybuf;// Pointer to constant dataprintf_s("%s\n",bptr);// *bptr = 'a';? // Error}
2.3 const 修飾成員函數(shù)
在聲明成員函數(shù)時(shí)艰毒,如果在函數(shù)末尾使用 const 關(guān)鍵字筐高,那么可以稱這個(gè)函數(shù)是”只讀”函數(shù)。 const成員函數(shù)不能修改任何 非static的成員變量丑瞧, 也不能調(diào)用任何 非const 成員函數(shù)柑土。
const成員函數(shù)在聲明和定義時(shí),都必須帶有 const 關(guān)鍵字绊汹』粒看下面這個(gè)例子:
// constant_member_function.cppclassDate{public:Date(intmn,intdy,intyr);intgetMonth()const;// A read-only functionvoidsetMonth(intmn);// A write function; can't be constprivate:intmonth;};intDate::getMonth()const{returnmonth;// Doesn't modify anything}voidDate::setMonth(intmn){month=mn;// Modifies data member}intmain(){DateMyDate(7,4,1998);constDateBirthDate(1,18,1953);MyDate.setMonth(4);// OkayBirthDate.getMonth();// OkayBirthDate.setMonth(4);// C2662 Error}
Part 3:new 和 delete
3.1 分解 new 和 delete
new 和 delete 都是表達(dá)式,因此不能被重載西乖。它們均有不同步驟組成:
new 的執(zhí)行步驟:
調(diào)用operator new 分配內(nèi)存 (malloc)
對(duì)指針進(jìn)行類型轉(zhuǎn)換
調(diào)用構(gòu)造函數(shù)
delete 的執(zhí)行步驟:
調(diào)用析構(gòu)函數(shù)
調(diào)用operator delete釋放內(nèi)存 (free)
雖然诫欠,new 和 delete 不能被重載,但是 operator new 和 operator delete 可以被重載浴栽。 更多細(xì)節(jié)查看msdn 上的相關(guān)頁(yè)面。 關(guān)于重寫 operator new/delete的一些原因轿偎,參考Customized Allocators with Operator New and Operator Delete典鸡。
3.2 重載 operator new 和 operator delete
3.2.1 重載全局 operator new 和 operator delete
用戶可以通過(guò)重新定義 全局 new 和 delete 操作符,以便通過(guò)日志或其它方式記錄內(nèi)存的分配和釋放坏晦。 其中一個(gè)應(yīng)用場(chǎng)景是用于檢查內(nèi)存泄漏萝玷。代碼如下:
// 這段代碼來(lái)自于 msdn:https://msdn.microsoft.com/en-us/library/kftdy56f.aspx// spec1_the_operator_delete_function1.cpp
// compile with: /EHsc
// arguments: 3#include
#include
#include
#include usingnamespacestd;intfLogMemory=0;// Perform logging (0=no; nonzero=yes)?intcBlocksAllocated=0;// Count of blocks allocated.// User-defined operator new.void*operatornew(size_tstAllocateBlock){staticintfInOpNew=0;// Guard flag.if(fLogMemory&&!fInOpNew){fInOpNew=1;clog<<"Memory block "<<++cBlocksAllocated<<" allocated for "<1)for(inti=0;i
編譯并運(yùn)行這段代碼嫁乘,可以看到如下輸出:
oscar@ubuntu:~/$ g++ -o main spec1_the_operator_delete_function1.cpp -lm
oscar@ubuntu:~/$ ./main 3
Memory block 1 allocated for 10 bytes
Memory block 1 deallocated
Memory block 1 allocated for 10 bytes
Memory block 1 deallocated
Memory block 1 allocated for 10 bytes
Memory block 1 deallocated
故事到這里還沒(méi)有結(jié)束,細(xì)心的童鞋可能會(huì)發(fā)現(xiàn):創(chuàng)建和釋放 char* pMem 時(shí)球碉,使用的分別是 operator new[] (size_t) 和 operator delete[] (void*), 并沒(méi)有調(diào)用 operator new 和 operator delete蜓斧。打印的結(jié)果卻告訴我:operator new 和 operator delete 確實(shí)被調(diào)用了(作驚恐狀)!U龆挎春!
這里,我找到了 cpluscplus.com 上關(guān)于 operator new[] 的表述豆拨。不解釋直奋,直接上圖:
關(guān)于重新定義 operator new[] 和 operator delete[],參考 msdn上new and delete Operators頁(yè)面最下方類成員函數(shù) operator new[] 和 operator delete[] 的實(shí)現(xiàn)施禾,它們是類似的脚线。
3.2.2 重載類的成員函數(shù) operator new 和 operator delete
上面我們介紹了重寫全局 operator new、operator new[]弥搞、operator delete邮绿、operator delete[] 的覆蓋 (override)。 下面我們看看 類作用域下這四個(gè)函數(shù)如何實(shí)現(xiàn)攀例,應(yīng)用場(chǎng)景以及注意事項(xiàng)船逮。
在類中重寫 operator new/delete([]) 成員函數(shù)時(shí),必須聲明它們?yōu)?static肛度,因此不能聲明為虛函數(shù)傻唾。
下面給出一個(gè)重寫類 operator new/delete 方法的例子:
// https://msdn.microsoft.com/en-us/library/kftdy56f.aspx
// spec1_the_operator_new_function1.cpp#include
#include
#include
#include usingnamespacestd;classBlanks{public:Blanks(){}Blanks(intdummy){throw1;}staticvoid*operatornew(size_tstAllocateBlock);staticvoid*operatornew(size_tstAllocateBlock,charchInit);staticvoid*operatornew(size_tstAllocateBlock,doubledInit);staticvoidoperatordelete(void*pvMem);staticvoidoperatordelete(void*pvMem,charchInit);staticvoidoperatordelete(void*pvMem,doubledInit);};void*Blanks::operatornew(size_tstAllocateBlock){clog<<"Blanks::operator new( size_t )\n";void*pvTemp=malloc(stAllocateBlock);returnpvTemp;}void*Blanks::operatornew(size_tstAllocateBlock,charchInit){clog<<"Blanks::operator new( size_t, char )\n";// throw 20;void*pvTemp=malloc(stAllocateBlock);if(pvTemp!=0)memset(pvTemp,chInit,stAllocateBlock);returnpvTemp;}void*Blanks::operatornew(size_tstAllocateBlock,doubledInit){clog<<"Blanks::operator new( size_t, double)\n";returnmalloc(stAllocateBlock);}voidBlanks::operatordelete(void*pvMem){clog<<"Blanks::opeator delete (void*)\n";free(pvMem);}voidBlanks::operatordelete(void*pvMem,charchInit){clog<<"Blanks::opeator delete (void*, char)\n";free(pvMem);}voidBlanks::operatordelete(void*pvMem,doubledInit){clog<<"Blanks::opeator delete (void*, double)\n";free(pvMem);}// For discrete objects of type Blanks, the global operator new function
// is hidden. Therefore, the following code allocates an object of type
// Blanks and initializes it to 0xa5intmain(){Blanks*a5=new('c')Blanks;deletea5;cout<
linux運(yùn)行上的代碼,結(jié)果如下:
Blanks::operator new( size_t, char )
Blanks::opeator delete (void*)
Blanks::operator new( size_t )
Blanks::opeator delete (void*)
Blanks::operator new( size_t, double)
terminate called after throwing an instance of 'int'
Aborted (core dumped)
很容易發(fā)現(xiàn)承耿,不管我們使用哪個(gè)版本的 operator new冠骄,最后調(diào)用的都是 不含額外的參數(shù)的 operator delete。 構(gòu)造函數(shù)拋出異常時(shí)加袋,也沒(méi)有調(diào)用對(duì)應(yīng)的 operator delete 成員函數(shù)凛辣。 那么包含額外參數(shù)的 delete什么時(shí)候會(huì)被調(diào)用到,應(yīng)用場(chǎng)景由有哪些呢职烧?
我們繼續(xù)找相關(guān)的文檔扁誓,msdn上有這樣一段文字:
voidoperatordelete(void*);voidoperatordelete(void*,size_t);
Only one of the preceding two forms can be present for a given class. The first form takes a single argument of type void *, which contains a pointer to the object to deallocate. The second form—sized deallocation—takes two arguments, the first of which is a pointer to the memory block to deallocate and the second of which is the number of bytes to deallocate. The return type of both forms is void (operator delete cannot return a value).
The intent of the second form is to speed up searching for the correct size category of the object to be deleted, which is often not stored near the allocation itself and likely uncached; the second form is particularly useful when an operator delete function from a base class is used to delete an object of a derived class.
這里的解釋也有些問(wèn)題,通過(guò)上面的例子蚀之,可以推斷 operator new/delete 均可以被重載蝗敢。 創(chuàng)建對(duì)象時(shí),可以使用不同版本的operator new足删,但是銷毀時(shí)寿谴,只調(diào)用不包含額外參數(shù)的operator delete。 delete 的應(yīng)用場(chǎng)景之一是:在繼承體系中失受,Base* 指向一個(gè)子類對(duì)象讶泰,調(diào)用 delete 銷毀該對(duì)象時(shí)咏瑟,必須保證銷毀父類對(duì)象,而不是根據(jù)子類對(duì)象的大小進(jìn)行截?cái)噤N毀痪署。
事實(shí)上码泞,上面所說(shuō)的應(yīng)用場(chǎng)景也沒(méi)有得到驗(yàn)證。我對(duì)上面的代碼進(jìn)行了修改狼犯,銷毀時(shí)調(diào)用的仍然是不含額外參數(shù)的 delete:
// https://msdn.microsoft.com/en-us/library/kftdy56f.aspx
// spec1_the_operator_new_function1.cpp#include
#include
#include
#include usingnamespacestd;classBase{public:virtual~Base(){}};classBlanks:publicBase{// ...? 沒(méi)有改變 ...};intmain(){Base*a5=new('c')Blanks;// 打印 Blanks::operator new( size_t, char )deletea5;// 打印 Blanks::opeator delete (void*)}
根據(jù)侯捷老師關(guān)于 basic_string 的分析余寥,operator delete 并沒(méi)有傳入額外的參數(shù),而是通過(guò) Allocator::deallocate 去刪除辜王。 因此 重載 operator delete 沒(méi)有任何意義劈狐,需要時(shí) 重新定義 operator delete(void* p)即可。 需要查看 stl 文章和源碼的話呐馆,可以去Code Project和sgi網(wǎng)站上查看肥缔。
注意:為類定義 operator new/delete 成員函數(shù)會(huì)覆蓋 全局默認(rèn)的 operator new/delete。 如果要使用默認(rèn)的 operator new/delete汹来,那么在創(chuàng)建對(duì)象和銷毀對(duì)象時(shí)续膳,需要使用 ::new 和 ::delete。
課程目錄:
一收班、對(duì)象模型:vptr和vtbl
二坟岔、對(duì)象模型:關(guān)于this
三、對(duì)象模型:關(guān)于Dynamic Binding
四摔桦、談?wù)刢onst
五社付、關(guān)于new,delete
六邻耕、重載operator new,operator delete,operator new[],operator delete[]
七鸥咖、示例
八、重載new(),delete()$示例
九兄世、Basic_String使用new(extra)擴(kuò)充申請(qǐng)量