簡體   English   中英

何時虛擬函數被靜態調用?

[英]When virtual functions are invoked statically?

直接從派生類指針調用虛擬函數與從基類指針調用同一個派生類的虛擬函數之間有什么性能差異?

在派生指針的情況下,調用是靜態綁定還是動態綁定? 我認為它將是動態綁定的,因為不能保證派生指針實際上並不指向另一個派生類。 如果我直接通過值(而不是通過指針或引用)來派生類,情況會改變嗎? 因此這3種情況:

  1. 派生的基本指針
  2. 派生指針
  3. 價值衍生

我擔心性能,因為代碼將在微控制器上運行。

演示代碼

struct Base {
    // virtual destructor left out for brevity
    virtual void method() = 0;
};

struct Derived : public Base {
    // implementation here
    void method() {
    }
}

// ... in source file
// call virtual method from base class pointer, guaranteed vtable lookup
Base* base = new Derived;
base->method();

// call virtual method from derived class pointer, any difference?
Derived* derived = new Derived;
derived->method();

// call virtual method from derived class value
Derived derivedValue;
derived.method();
  • 從理論上講,唯一起作用的C ++語法是使用合格成員名稱的成員函數調用。 就您的類定義而言,

     derived->Derived::method(); 

    該調用將忽略對象的動態類型,並直接轉到Derived::method() ,即它是靜態綁定的。 這僅可用於調用在類本身或其祖先類之一中聲明的方法。

    其他所有內容都是常規的虛函數調用,它根據調用中使用的對象的動態類型來解析,即它是動態綁定的。

  • 在實踐中,編譯器將努力優化代碼,並在編譯時知道對象的動態類型的上下文中,用靜態綁定調用替換動態綁定調用。 例如

     Derived derivedValue; derivedValue.method(); 

    即使語言規范沒有針對這種情況提供任何特殊處理,它實際上也會在幾乎所有現代編譯器中產生靜態綁定調用。

    同樣,直接從構造函數和析構函數直接進行的虛擬方法調用通常會編譯為靜態綁定的調用。

    當然,智能編譯器可能能夠在更多種類的上下文中靜態綁定調用。 例如,兩者

     Base* base = new Derived; base->method(); 

     Derived* derived = new Derived; derived->method(); 

    編譯器可以將其視為瑣碎的情況,可以輕松地進行靜態綁定的調用。

虛擬函數必須進行編譯才能像始終被虛擬調用一樣工作。 如果您的編譯器將虛擬調用編譯為靜態調用,則這是必須滿足此前提條件的優化。

由此可見,編譯器必須能夠證明所討論對象的確切類型。 有一些有效的方法可以做到這一點:

  • 如果編譯器看到對象的創建(從中獲取地址的new表達式或自動變量),並可以證明該創建實際上是當前指針值的源,則可以為其提供所需的精確動態類型。 您的所有示例均屬於此類。

  • 運行構造函數時,對象的類型恰好是包含正在運行的構造函數的類。 因此,可以靜態解析構造函數中進行的任何虛擬函數調用。

  • 同樣,在運行析構函數時,對象的類型恰好是包含正在運行的析構函數的類。 同樣,任何虛擬函數調用都可以靜態解決。

Afaik,所有這些情況都允許編譯器將動態調度轉換為靜態調用。

所有這些都是優化,盡管如此,編譯器仍可能決定執行運行時vtable查找。 但是好的優化編譯器應該能夠檢測所有三種情況。

前兩種情況之間應該沒有區別,因為虛擬函數的基本思想是始終調用實際的實現。 不考慮編譯器優化(理論上,如果您在同一編譯單元中構造對象,則可以優化所有虛擬函數調用,並且無法在兩者之間更改指針),第二個調用必須實現為間接(虛擬) )調用,因為可能會有一個第三類繼承自Derived並實現該功能。 我認為第三個調用將不是虛擬的,因為編譯器在編譯時就已經知道了實際的類型。 實際上,如果您知道始終將直接在派生類上進行調用,則可以通過不將函數定義為虛函數來確保這一點。

對於在小型微控制器上運行的真正輕量級的代碼,我建議完全避免將函數定義為虛擬函數。 通常不需要運行時抽象。 如果您編寫一個庫並需要某種抽象,則可以改為使用模板(為您提供一些編譯時抽象)。

至少在PC CPU上,我經常發現虛擬調用是您可以擁有的最昂貴的間接調用之一(可能是因為分支預測更加困難)。 有時,也可以將間接轉換為數據級別,例如,您保留了一個通用函數,該通用函數可對不同數據進行操作,而這些數據是通過指向實際實現的指針間接實現的。 當然,這僅在某些非常特殊的情況下有效。

在運行時。

但是:性能相比呢? 將虛擬函數調用與非虛擬函數調用進行比較是無效的。 您需要將其與非虛擬函數調用以及ifswitch ,間接調用或其他提供相同功能的方法進行比較。 如果該功能未在實現中體現出選擇,即不需要是虛擬的,則不要使其成為虛擬的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM