程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++:條款30:透徹了解inlining的裡裡外外

Effective C++:條款30:透徹了解inlining的裡裡外外

編輯:C++入門知識

(一)

inline函數,可以調用它們而又不需蒙受函數調用所招致的額外開銷。

inline函數背後的整體觀念是,將“對此函數的每一個調用”都已函數本體替換之,這樣做可能增加你的目標碼(object code)大小。在內存有限的機器上,過度inline會造成程序體積太大,導致換頁行為,降低緩存的命中率等一些帶來效率損失的行為。如果inline函數的本體很小,編譯器針對“函數本體”所產生的碼可能比針對“函數調用”所產出的碼更小。將函數inline可以導致更小的目標碼,從而提高效率。


(二)

inline只是對編譯器的一個申請,不是強制命令。這種申請可以隱喻提出也可以明確提出。

(1)隱喻提出(隱喻方式是將函數定義於class定義式內):

class Person { 
public: 
    ... 
    int age() const {return theAge;}//一個隱喻的inline申請 
    ... 
private: 
    int theAge; 
};
(2)明確提出(明確申請inline函數的做法是在其定義式前加上關鍵字inline):

template 
inline const T& std::max(const T& a, const T& b) { 
    return a < b? b: a; 
}


(三)

(1)如果我們正在寫一個template而我們認為素有根據此template具現出來的函數都應該inlined,那麼請將此template聲明為inline;

如果template沒有理由要求它所具現的每個函數都是inlined,就應該避免將這個template聲明為inline(不論顯式還是隱式)。inline需要成本。

(2)大部分編譯器拒絕將太過復雜(例如帶有循環或遞歸)的函數inlining,而所有對virtual函數的調用也都會使inlining落空。因為virtual意味著“等待,直到運行期才確定調用哪個函數”,而inline意味“執行前,先將調用動作替換為被調用函數的本體”。如果編譯器不知道該調用哪個函數,那肯定就沒法inlining了!

(3)有時,雖然編譯器有意願inlining某個函數,但還是可能為該函數生成一個函數本體。例如,如果程序要取某個inline函數的地址,因為編譯器通常必須為此函數生成一個outlined函數本體(畢竟編譯器沒有能力提出一個指針指向並不存在的函數),所以編譯器通常不對“通過函數指針而進行的調用”實施inlining。

inline void f(){…} //假設編譯器有意願inline“對f的調用”
void (*pf)() = f;
f();//這個調用將被inlined,因為是一個正常調用
pf();//這個調用或許不被inlined,因為通過指針達成

(四)

class base { 
public: 
    ... 
private: 
    std::string bm1, bm2; 
};

class Derived : public Base { 
public: 
    Derived(){ }  //Derived 構造函數是空的 是嗎? 
    ... 
private: 
    std::string dm1, dm2, dm3; 
};

這個構造函數看起來是inlining的絕佳候選人,因為他根本不含任何代碼,但是:

C++對於“對象被創建和被銷毀時發生什麼事”做了各式各樣的保證。編譯器為稍早說的那個表面上看起來是空的Derived構造函數所產生的代碼,相當於以下所列:

Derived::Derived() { 
   Base::Base(); 
    try{dm1.std::string::string();} 
    catch(...){ 
        Base::~Base(); 
        throw; 
    } 
    try{dm2.std::string::string();} 
    catch(...){ 
        dm1.std::string::~string(); 
        Base::~Base(); 
        throw; 
    } 
    try{dm3.std::string::string();} 
    catch(...){ 
        dm2.std::string::~string(); 
        dm1.std::string::~string(); 
        Base::~Base(); 
        throw; 
    } 
}
這段代碼並不能代表編譯器真正制造出來的代碼,因為真正的編譯器會以更精致復雜的做法來處理異常.盡管如此,這已能准確反映Derived的空白構造函數必須提供的行為。Derived構造函數至少一定會陸續調用其成員變量和baseclass兩者的構造函數,而那些調用(它們自身也可能被inlined)會影響編譯器是否對此空白函數inlining。

(五)

程序庫設計者必須評估"將函數聲明為inline"的沖擊:inline函數無法隨著程序庫的升級而升級。

客戶將“f函數本體”編進其程序中,一旦程序庫設計者決定改變f,所有用到f的客戶端程序都必須重新編譯。然而若f是non-inline函數,客戶端只要重新連接就好了,如果是程序庫采用動態鏈接,升級後的函數甚至可以不知不覺的被應用程序吸納。

請記住:

(1)將大多數inlining限制在小型,被頻繁調用的函數身上.這可使日後的調試過程和二進制升級更容易。也可使潛在的代碼膨脹問題最小化,使程序的速度提升機會最大化。

(2) 不要因為function templates出現在頭文件,就將它們聲明為inline。




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