程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++如何處理內聯虛函數

C++如何處理內聯虛函數

編輯:關於C++

當一個函數是內聯和虛函數時,會發生代碼替換或使用虛表調用嗎? 為了弄 清楚內聯和虛函數,讓我們將它們分開來考慮。通常,一個內聯函數是被展開的 。

class CFoo {
    private:
      int val;
    public:
      int GetVal() { return val; }
      int SetVal(int v) { return val=v; }
    };

這裡,如果使用下列代碼:

CFoo x;
x.SetVal(17);
int y = x.GetVal();

那麼編譯器產生的目標代碼將與下面的代碼段一樣:

CFoo x;
x.val = 17;
int y = x.val;

你當然不能這麼做,因為val是個私有變量。內聯函數的優點是不用函數調用 就能隱藏數據,僅此而已。

虛函數有多態性,意味著派生的類能實現相同的函數,但功能卻不同。假設 GetVal 被聲明為虛函數,並且你有第二個 以不同方法實現的類 CFoo2:

class CFoo2 : public CFoo {
    public:
    // virtual in base class too!
    virtual int CFoo2::GetVal() {return someOtherVal;}
    };

如果 pFoo是一個 CFoo 或 CFoo2 指針,那麼,無論 pFoo 指向哪個類 CFoo 或 CFoo2,成員函數 pFoo->GetVal 都能調用成功。

如果一個函數既是虛擬函數,又是內聯函數,會是什麼情況呢?記住,有兩 種方式建立內聯函數,

第一種是在函數定義中使用關鍵字 inline,如:

inline CFoo::GetVal() { return val; }

第二種是在類的聲明中編寫函數體 ,就象前面的 CFoo2::GetVal 一樣。所以如果將虛函數體包含在類的聲明中, 如:

class CFoo {
    public:
    virtual int GetVal() { return val; }
    };

編譯器便認為這個函數 GetVal 是內聯的,同時也是虛擬 的。那麼,多態性和內聯特性如何同時工作呢?

編譯器遵循的第一個規則是無論發生什麼事情,多態性必須起作用。如果有 一個指向 CFoo 對象的指針,pFoo->GetVal 被保證去調用正確的函數。一般 情況下,這就是說函數 GetVal 將被實例化為非內聯函數,並有vtable(虛表) 入口指向它們。但這並不意味著這個函數不能被擴展!再看看下面的代碼:

CFoo x;
x.SetVal(17)
int y = x.GetVal()

編譯器知道x是 CFoo,而不是CFoo2,因為這個堆對象是被顯式聲明的。x肯定 不會是CFoo2。所以展開 SetVal/GetVal 內聯是安全的。如果要寫更多的復雜代 碼:

CFoo x;
  CFoo* pfoo=&x;
  pfoo->SetVal(17);
  int y = pfoo->GetVal();
  ...
  CFoo2 x2;
  pfoo = &x2;
  pfoo->SetVal(17); //etc.

編譯器知道 pfoo 第一次指向x, 第二次指向x2,所以展開虛擬函數也是安全的。

你還可以編寫更復雜的代碼,其中,pfoo 所指的對象類型總是透明的,但是 大多數編譯器不會做任何更多的分析。即使在前面的例子中,某些編譯器將會安 全運行,實例化並通過一個虛表來調用。實際上,編譯器總是忽略內聯需要並總 是使用虛表。唯一絕對的規則是代碼必須工作;也就是說,虛函數必須有多態行 為。

通常,無論是顯式還是隱式內聯,它只是一個提示而已,並非是必須的,就 象寄存器一樣。編譯器完全能拒絕展開一個非虛內聯函數,C++編譯器常常首先 會報錯:“內聯中斷-函數太大”。如果內聯函數調用自身,或者你 在某處傳遞其地址,編譯器必須產生一個正常(外聯?)函數。內聯函數在 DEBUG BUILDS中不被展開,可設置編譯選項來預防。

要想知道編譯器正在做什麼,唯一的方法是看它產生的代碼。對於微軟的編 譯器來說,你可以用-FA編譯選項產生匯編清單。你不必知道匯編程序如何做。 我鼓勵你完成這個實驗;這對於了解機器實際所做的事情機器有益,同時你可學 習許多匯編列表中的內容。

有關內聯函數的東西比你第一次接觸它時要復雜得多。有許多種情況強迫編 譯器產生正常函數:遞歸,獲取函數地址,太大的那些函數和虛函數。但是如果 編譯器決定實例化你的內聯函數,就要考慮把函數放在什麼地方?它進入哪個模 塊?

通常類在頭文件中聲明,所以如果某個cpp包含foo.h,並且編譯器決定實例 化CFoo::GetVal,則在cpp文件中將它實例化成一個靜態函數。如果十個模塊包 含foo.h,編譯器產生的虛函數拷貝就有十個。實際上,可以用虛表指向不同類 型的GetVal拷貝,從而是相同類型的對象只產生拷貝。一些鏈接器能巧妙地在鏈 接時排除冗余,但一般你是不能指望他來保證的。

我們得出的結論是:最好不要使用內聯虛函數,因為它們幾乎不會被展開, 即便你的函數只有一行,你最好還是將它與其它的類函數一起放在模塊(cpp文 件)中。當然,開發者常常將簡短的虛函數放在類聲明中-不是因為他們希望這 個函數被展開為內聯,而是因為這樣做更方便和可讀性更強。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved