【C++】虛函數原理

【參考原文】Ryan in C++:C++虛函數原理
類中的成員函數分爲靜態成員函數和非靜態成員函數,而非靜態成員函數又分爲普通函數和虛函數。

Q: 爲什麼使用虛函數

A: 使用虛函數,我們可以獲得良好的可擴展性。在一個設計比較好的面向對象程序中,大多數函數都是與基類的接口進行通信。因爲使用基類接口時,調用基類接口的程序不需要改變就可以適應新類。如果用戶想添加新功能,他就可以從基類繼承並添加相應的新功能。

Q: 簡述C++虛函數作用及底層實現原理

A: 要點是要答出虛函數表和虛函數表指針的作用。

虛函數是用來實現動態綁定的。

C++中虛函數使用虛函數表和虛函數表指針實現,虛函數表是一個類的虛函數的地址表,用於索引類本身以及父類的虛函數的地址,假如子類重寫了父類的虛函數,則對應在虛函數表中會把對應的虛函數替換爲子類的函數的地址(子類中可以不是虛函數,但是必須同名);虛函數表指針存在於每個對象中(通常出於效率考慮,會放在對象的開始地址處),它指向對象所在類的虛函數表的地址;在多繼承環境下,會存在多個虛函數表指針,分別指向對應不同基類的虛函數表。


虛函數表是每個(有虛函數的)類對應一個。虛函數表指針是每個對象對應一個。

虛函數表裏只能存放虛函數,不能存放普通函數。

如果一個函數不是虛函數,那麼對它的調用(即該函數的地址)在編譯階段就會確定。調用虛函數的話(它的地址)要運行時才能確定。

虛函數的函數入口是動態綁定的。在運行時,程序根據基類指針指向的實際對象,來調用該對象對應版本的函數。(用該對象的虛函數表指針找到其虛函數表,進而調用不同的函數。)(只有是虛函數的情況下才會這麼做(用虛函數表指針去查虛函數表)。非虛函數直接就調用自己的。)

follow up:

  1. 爲什麼需要虛析構函數?(什麼情況下要用虛析構函數?)

在存在類繼承並且析構函數中需要析構某些資源時,析構函數需要是虛函數。否則若使用父類指針指向子類對象,在delete時只會調用父類的析構函數,而不能調用子類的析構函數,造成內存泄露。

  1. 一個對象訪問普通成員函數和虛函數哪個更快?

訪問普通成員函數更快,因爲普通成員函數的地址在編譯階段就已確定,因此在訪問時直接調用對應地址的函數;

而虛函數在調用時,需要首先在虛函數表中尋找虛函數所在地址,因此相比普通成員函數速度要慢一些。

  1. 析構函數一定是虛函數嗎?

不一定。1. 虛函數效率相對要低一些;2. 有些類並沒有子類,沒必要用虛析構函數。

  1. 內聯函數、構造函數、靜態成員函數可以是虛函數嗎?

都不可以。

內聯函數(inline)需要在編譯階段展開(在編譯時就已經確定了),而虛函數是運行時動態綁定的,編譯時無法展開,因此是矛盾的;

構造函數在進行調用時還不存在父類和子類的概念,父類只會調用父類的構造函數,子類調用子類的,因此不存在動態綁定的概念(先有父類纔能有子類,構造父類的時候子類還不存在,子類都還沒有怎麼可能在父類裏動態調用子類);

  1. 構造函數中可以調用虛函數嗎?

    可以,但是沒有意義,起不到動態綁定的效果。父類構造函數中調用的仍然是父類版本的函數,子類中調用的仍然是子類版本的函數。

  2. 簡述C++中虛繼承的作用及底層實現原理?

虛繼承用於解決多繼承條件下的菱形繼承問題,底層實現原理與編譯器相關,一般通過虛基類指針實現,即各對象中只保存一份父類的對象,多繼承時通過虛基類指針引用該公共對象,從而避免菱形繼承中的二義性問題。

發佈了22 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章