C++面試八股文之override和finial關(guān)鍵字有何作用
某日二師兄參加X(jué)XX科技公司的C++工程師開(kāi)發(fā)崗位第22面: (二師兄好苦逼,節(jié)假日還在面試。。。)
面試官:C++的繼承了解嗎?
二師兄:(不好意思,你面到我的強(qiáng)項(xiàng)了。。)了解一些。
面試官:什么是虛函數(shù),為什么需要虛函數(shù)?
二師兄:虛函數(shù)允許在基類中定義一個(gè)函數(shù),然后在派生類中進(jìn)行重寫(xiě)(
override
)。二師兄:主要是為了實(shí)現(xiàn)面向?qū)ο笾械娜筇匦灾欢鄳B(tài)。多態(tài)允許在子類中重寫(xiě)父類的虛函數(shù),同樣的函數(shù)在子類和父類實(shí)現(xiàn)不同的形態(tài),簡(jiǎn)稱為多態(tài)。
面試官:你知道
override
和finial
關(guān)鍵字的作用嗎?二師兄:
override
關(guān)鍵字告訴編譯器,這個(gè)函數(shù)一定會(huì)重寫(xiě)父類的虛函數(shù),如果父類沒(méi)有這個(gè)虛函數(shù),則無(wú)法通過(guò)編譯。此關(guān)鍵字可省略,但不建議省略。二師兄:
finial
關(guān)鍵字告訴編譯器,這個(gè)函數(shù)到此為止,如果后續(xù)有類繼承當(dāng)前類,也不能再重寫(xiě)此函數(shù)。二師兄:這兩個(gè)關(guān)鍵字都是C++11引入的,為了提升C++面向?qū)ο缶幋a的安全性。
面試官:你知道多態(tài)是怎么實(shí)現(xiàn)的嗎?
二師兄:(起開(kāi),我要開(kāi)始裝逼了?。〤++主要使用了虛指針和虛表來(lái)實(shí)現(xiàn)多態(tài)。在擁有虛函數(shù)的對(duì)象中,包含一個(gè)虛指針(
virtual pointer
)(一般位于對(duì)象所在內(nèi)存的起始位置),這個(gè)虛指針指向一個(gè)虛表(virtual table
),虛表中記錄了虛函數(shù)的真實(shí)地址。
#include <iostream> struct Foo { size_t a = 42; virtual void fun1() {std::cout <<"Foo::fun1" << std::endl;} virtual void fun2() {std::cout <<"Foo::fun2" << std::endl;} virtual void fun3() {std::cout <<"Foo::fun3" << std::endl;} }; struct Goo: Foo{ size_t b = 1024; virtual void fun1() override {std::cout <<"Goo::fun1" << std::endl;} virtual void fun3() override {std::cout <<"Goo::fun3" << std::endl;} }; using PF = void(*)(); void test(Foo* pf) { size_t* virtual_point = (size_t*)pf; PF* pf1 = (PF*)*virtual_point; PF* pf2 = pf1 + 1; //偏移8字節(jié) 到下一個(gè)指針 fun2 PF* pf3 = pf1 + 2; //偏移16字節(jié) 到下下一個(gè)指針 fun3 (*pf1)(); //Foo::fun1 or Goo::fun1 取決于pf的真實(shí)類型 (*pf2)(); //Foo::fun2 (*pf3)(); //Foo::fun3 or Goo::fun3 取決于pf的真實(shí)類型 } int main(int argc, char const *argv[]) { Foo* fp = new Foo; test(fp); fp = new Goo; test(fp); size_t* virtual_point = (size_t*)fp; size_t* ap = virtual_point + 1; size_t* bp = virtual_point + 2; std::cout << *ap << std::endl; //42 std::cout << *bp << std::endl; //1024 }
二師兄:當(dāng)初始化虛表時(shí),會(huì)把當(dāng)前類override的函數(shù)地址寫(xiě)到虛表中(
Goo::fun1
、Goo::fun3
),對(duì)于基類中的虛函數(shù)但是派生類中沒(méi)有override
,則會(huì)把基類的函數(shù)地址寫(xiě)到虛表中(Foo::fun2
),在調(diào)用函數(shù)的時(shí)候,會(huì)通過(guò)虛指針轉(zhuǎn)到虛表,并根據(jù)虛函數(shù)的偏移得到真實(shí)函數(shù)地址,從而實(shí)現(xiàn)多態(tài)。面試官:不錯(cuò)。上圖你畫(huà)出了單一繼承的內(nèi)存布局,那多繼承呢?
二師兄:多繼承內(nèi)存布局類似,只不過(guò)會(huì)多幾個(gè)
virtual pointer
。
#include <iostream> struct Foo1 { size_t a = 42; virtual void fun1() {std::cout <<"Foo1::fun1" << std::endl;} virtual void fun2() {std::cout <<"Foo1::fun2" << std::endl;} virtual void fun3() {std::cout <<"Foo1::fun3" << std::endl;} }; struct Foo2{ size_t b = 1024; virtual void fun4() {std::cout <<"Foo2::fun4" << std::endl;} virtual void fun5() {std::cout <<"Foo2::fun5" << std::endl;} }; struct Foo3{ size_t c = 0; virtual void fun6() {std::cout <<"Foo3::fun1" << std::endl;} virtual void fun7() {std::cout <<"Foo3::fun3" << std::endl;} }; struct Goo: public Foo1, public Foo2, public Foo3 { virtual void fun2() override {std::cout <<"Goo::fun2" << std::endl;} virtual void fun6() override {std::cout <<"Goo::fun6" << std::endl;} }; int main(int argc, char const *argv[]) { Goo g; g.fun1(); //Foo1::fun1 g.fun2(); //Goo::fun2 g.fun3(); //Foo1::fun3 g.fun4(); //Foo2::fun4 g.fun5(); //Foo2::fun5 g.fun6(); //Goo::fun6 g.fun7(); //Foo3::fun7 }
面試官:你知道什么是菱形繼承嗎?菱形繼承會(huì)引發(fā)什么問(wèn)題?如何解決?
二師兄:菱形繼承(
Diamond Inheritance
)是指在繼承層次結(jié)構(gòu)中,如果兩個(gè)不同的子類B和C繼承自同一個(gè)父類A,而又有一個(gè)類D同時(shí)繼承B和C,這種繼承關(guān)系被稱為菱形繼承。
二師兄:因?yàn)锽和C各繼承了一份A,當(dāng)D繼承B和C的時(shí)候就會(huì)有2份A;
#include <iostream> struct A { int val = 42; virtual void fun(){std::cout <<"A::fun" << std::endl;} }; struct B: public A{ void fun() override{std::cout <<"B::fun" << std::endl;}}; struct C: public A{ void fun() override{std::cout <<"C::fun" << std::endl;}}; struct D: public B, public C{void fun() override{std::cout <<"D::fun" << std::endl;}}; int main(int argc, char const *argv[]) { D d; std::cout << d.val << std::endl; //編譯失敗,不知道調(diào)用從哪個(gè)類中繼承的val變量 d.fun(); //編譯失敗,不知道調(diào)用從哪個(gè)類中繼承的fun函數(shù) }
二師兄:解決的辦法有兩種,一種是在調(diào)用符之前加上父類限定符:
std::cout << d.B::val << std::endl; //42 d.C::fun(); //C::fun
二師兄:但這里并沒(méi)有解決數(shù)據(jù)冗余的問(wèn)題,因?yàn)镈中有B和C,而B(niǎo)和C各有一個(gè)虛表和一個(gè)int類型的成員變量,所以
sizeof(D)
的大小是32(x86_64
架構(gòu),考慮到內(nèi)存對(duì)齊)。二師兄:所幸在C++11引入了虛繼承(
Virtual Inheritance
)機(jī)制,從源頭上解決了這個(gè)問(wèn)題:
#include <iostream> struct A { int val = 42; virtual void fun(){std::cout <<"A::fun" << std::endl;} }; struct B: virtual public A{ void fun() override{std::cout <<"B::fun" << std::endl;}}; struct C: virtual public A{ void fun() override{std::cout <<"C::fun" << std::endl;}}; struct D: public B, public C{void fun() override{std::cout <<"D::fun" << std::endl;}}; int main(int argc, char const *argv[]) { D d; std::cout << d.val << std::endl; //42 d.fun(); //D::fun }
二師兄:此時(shí)在對(duì)象
d
中,只包含了一個(gè)val
和兩個(gè)虛指針,成員變量的冗余問(wèn)題得到解決。面試官:一般我們認(rèn)為多態(tài)會(huì)影響性能,你舉得為什么影響性能?
二師兄:大多數(shù)人認(rèn)為,虛函數(shù)的調(diào)用會(huì)先通過(guò)虛指針跳到虛函數(shù)表,然后通過(guò)偏移確定函數(shù)真實(shí)地址,再跳轉(zhuǎn)到地址執(zhí)行,是間接調(diào)用導(dǎo)致了性能損失。
二師兄:但實(shí)際上無(wú)法內(nèi)聯(lián)才是虛函數(shù)性能低于正常函數(shù)的主要原因。由于多態(tài)是運(yùn)行時(shí)特征,在編譯時(shí)編譯器并不知道指針指向的函數(shù)地址,所以無(wú)法被內(nèi)聯(lián)。同時(shí)跳轉(zhuǎn)到特定地址執(zhí)行函數(shù)可能引發(fā)的
L1 cache miss
(空間局部性不好),這也會(huì)影響性能。面試官:虛函數(shù)的調(diào)用一定是非內(nèi)聯(lián)的嗎?
二師兄:不是?,F(xiàn)代編譯器很聰明,如果編譯器能夠在編譯時(shí)推斷出真實(shí)的函數(shù),可能會(huì)直接內(nèi)聯(lián)這個(gè)虛函數(shù)。虛函數(shù)的調(diào)用是否內(nèi)聯(lián)取決于編譯器的實(shí)現(xiàn)和上下文。
面試官:你覺(jué)得多態(tài)在安全性上有沒(méi)有什么問(wèn)題?
二師兄:的確是有的。當(dāng)我們把類中的虛函數(shù)定義為
private
的時(shí)候,雖然我們不能通過(guò)類的對(duì)象去訪問(wèn)這個(gè)函數(shù),但我們知道這個(gè)函數(shù)就在虛函數(shù)表中,可以通過(guò)特殊的方法(上文中已經(jīng)給出示例)訪問(wèn)它:
#include <iostream> struct Foo { private: virtual void fun() {std::cout << "Foo::fun" << std::endl;} }; int main(int argc, char const *argv[]) { Foo f; //f.fun(); //編譯錯(cuò)誤 using Fun = void(*)(); size_t* virtual_point = (size_t*)&f; Fun* fun = (Fun*)*virtual_point; (*fun)(); }
面試官:好的,今天的面試到這里就結(jié)束了,請(qǐng)回去等通知吧。
總結(jié)
到此這篇關(guān)于C++面試八股文之override和finial關(guān)鍵字有何作用的文章就介紹到這了,更多相關(guān)C++ override和finial關(guān)鍵字的作用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言編程內(nèi)存分配通訊錄靜態(tài)實(shí)現(xiàn)示例代碼教程
這篇文章主要為大家介紹了C語(yǔ)言編程實(shí)現(xiàn)靜態(tài)的通訊錄示例代碼教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-10-10C語(yǔ)言實(shí)現(xiàn)計(jì)算雙色球的中獎(jiǎng)率
這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言實(shí)現(xiàn)計(jì)算雙色球的中獎(jiǎng)率,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12C++?Cartographer源碼中關(guān)于傳感器的數(shù)據(jù)傳遞實(shí)現(xiàn)
這篇文章主要介紹了C++?Cartographer源碼中關(guān)于傳感器的數(shù)據(jù)傳遞實(shí)現(xiàn),前面已經(jīng)談到了Cartographer中添加軌跡的方法和傳感器的數(shù)據(jù)流動(dòng)走向。發(fā)現(xiàn)在此調(diào)用了LaunchSubscribers這個(gè)函數(shù)來(lái)訂閱相關(guān)傳感器數(shù)據(jù)2023-03-03C++下如何將TensorFlow模型封裝成DLL供C#調(diào)用
這篇文章主要介紹了C++下如何將TensorFlow模型封裝成DLL供C#調(diào)用問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11C語(yǔ)言實(shí)現(xiàn)BMP圖像閉運(yùn)算處理
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)BMP圖像閉運(yùn)算處理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10C++可執(zhí)行文件絕對(duì)路徑值與VS安全檢查詳解
這篇文章主要給大家介紹了關(guān)于C++可執(zhí)行文件絕對(duì)路徑值與VS安全檢查的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C++具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-01-01