實(shí)現(xiàn)C++虛函數(shù)時(shí)相關(guān)注意事宜
C++中的C++虛函數(shù)的作用主要是實(shí)現(xiàn)了多態(tài)的機(jī)制,虛函數(shù)(Virtual Function)是通過一張?zhí)摵瘮?shù)表(Virtual Table)來實(shí)現(xiàn)的,簡稱為V-Table,供大家學(xué)習(xí)參考!
如果一個(gè)類不包含虛函數(shù),這經(jīng)常預(yù)示不打算將它作為基類使用。當(dāng)一個(gè)類不打算作為基類時(shí),將析構(gòu)函數(shù)聲明為虛擬通常是個(gè)壞主意??紤]一個(gè)表現(xiàn)二維空間中的點(diǎn)的類:
- class Point { // a 2D point
- public:
- Point(int xCoord, int yCoord);
- ~Point();
- private:
- int x, y;
- };
如果一個(gè) int 占 32 位,一個(gè) Point 對象正好適用于 64 位的寄存器。而且,這樣一個(gè) Point 對象可以被作為一個(gè) 64 位的量傳遞給其它語言寫的函數(shù),比如 C 或者 FORTRAN。如果 Point 的析構(gòu)函數(shù)是虛擬的,情況就完全不一樣了。
C++虛函數(shù)的實(shí)現(xiàn)要求對象攜帶額外的信息,這些信息用于在運(yùn)行時(shí)確定該對象應(yīng)該調(diào)用哪一個(gè)虛函數(shù)。典型情況下,這一信息具有一種被稱為 vptr(virtual table pointer,虛函數(shù)表指針)的指針的形式。
vptr 指向一個(gè)被稱為 vtbl(virtual table,虛函數(shù)表)的函數(shù)指針數(shù)組,每一個(gè)包含C++虛函數(shù)的類都關(guān)聯(lián)到 vtbl。當(dāng)一個(gè)對象調(diào)用了虛函數(shù),實(shí)際的被調(diào)用函數(shù)通過下面的步驟確定:找到對象的 vptr 指向的 vtbl,然后在 vtbl 中尋找合適的函數(shù)指針。
虛函數(shù)如何被實(shí)現(xiàn)的細(xì)節(jié)是不重要的。重要的是如果 Point 類包含一個(gè)虛函數(shù),這個(gè)類型的對象的大小就會(huì)增加。在一個(gè) 32 位架構(gòu)中,它們將從 64 位(相當(dāng)于兩個(gè) int)長到 96 位(兩個(gè) int 加上 vptr);
在一個(gè) 64 位架構(gòu)中,他們可能從 64 位長到 128 位,因?yàn)樵谶@樣的架構(gòu)中指針的大小是 64 位的。為 Point 加上 vptr 將會(huì)使它的大小增長 50-100%!Point 對象不再適合 64 位寄存器。而且,Point 對象在 C++ 和其他語言(比如 C)中。
看起來不再具有相同的結(jié)構(gòu),因?yàn)槠渌Z言缺乏 vptr 的對應(yīng)物。結(jié)果,Points 不再可能傳入其它語言寫成的函數(shù)或從其中傳出,除非你為 vptr 做出明確的對應(yīng),而這是它自己的實(shí)現(xiàn)細(xì)節(jié)并因此失去可移植性。
這里的基準(zhǔn)就是不加選擇地將所有析構(gòu)函數(shù)聲明為虛擬,和從不把它們聲明為虛擬一樣是錯(cuò)誤的。實(shí)際上,很多人總結(jié)過這條規(guī)則:當(dāng)且僅當(dāng)類中至少包含一個(gè)虛擬函數(shù)時(shí),則聲明一個(gè)虛析構(gòu)函數(shù)。
但是,當(dāng)完全沒有C++虛函數(shù)時(shí),就可能和非虛析構(gòu)函數(shù)問題發(fā)生撕咬。例如,標(biāo)準(zhǔn) string 類型不包含C++虛函數(shù),但是被誤導(dǎo)的程序員有時(shí)將它當(dāng)作基類使用:
- SpecialString *pss = new SpecialString("Impending Doom");
- std::string *ps;
- ...
- ps = pss; // SpecialString* => std::string*
- ...
- delete ps; // undefined! In practice,
- // *ps’s SpecialString resources
- // will be leaked, because the
- // SpecialString destructor won’t
- // be called.
一眼看上去,這可能無傷大雅,但是,如果在程序的某個(gè)地方因?yàn)槟撤N原因,你將一個(gè)指向 SpecialString 的指針轉(zhuǎn)型為一個(gè)指向 string 的指針,然后你將 delete 施加于這個(gè) string 指針,你就立刻被送入未定義行為的領(lǐng)地。
【編輯推薦】