inline
inline是C++關鍵字,在函數聲明或定義中,函數返回類型前加上關鍵字inline,即可以把函數指定爲內聯函數。這樣可以解決一些頻繁調用的函數大量消耗棧空間(棧內存)的問題。關鍵字inline必須與函數定義放在一起才能使函數成爲內聯函數,僅僅將inline放在函數聲明前面不起任何作用。inline是一種“用於實現”的關鍵字,而不是一種“用於聲明”的關鍵字。
錯誤全局函數例子:function_inline
inline void function_inline();
void function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << std::endl;
}
正確全局函數例子:function_inline
inline void function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << std::endl;
}
錯誤成員函數例子:class_inline::function_inline
//class_inline.h
class class_inline
{
public:
inline void function_inline();
};
//class_inline.cpp
void class_inline::function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << std::endl;
}
正確成員函數例子:class_inline::function_inline
推薦!
//class_inline.h
class class_inline
{
public:
inline void function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << std::endl;
}
};
不推薦!
//class_inline.h
class class_inline
{
public:
inline void function_inline();
};
//class_inline.cpp
inline void class_inline::function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << std::endl;
}
不推薦!
//class_inline.h
class class_inline {
public:
void function_inline();
};
//class_inline.cpp
inline void class_inline::function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << std::endl;
}
inline函數僅僅是一個對編譯器的建議,所以最後能否真正內聯,看編譯器的意思,它如果認爲函數不復雜,能在調用點展開,就會真正內聯,並不是說聲明瞭內聯就會內聯,聲明內聯只是一個建議而已。
The extent to which suggestions made by using the inline function specifier are effective (C99 6.7.4).
- GCC will not inline any functions if the -fno-inline option is used or if -O0 is used. Otherwise, GCC may still be unable to inline a function for many reasons; the -Winline option may be used to determine if a function has not been inlined and why not.
運的是大多數編譯器提供了一個診斷級別:如果它們無法將你要求的函數 inline 化, 會給你一個警告信息。
inline限制
inline只適合函數體內簡單代碼的涵數使用。不能包含複雜的結構控制語句例如while、switch,並且不能內聯函數本身不能是直接遞歸函數(即,自己內部還調用自己的函數)。而所有(除了最平凡,幾乎什麼也沒做)的虛擬函數,都會阻止inline的進行。因爲virtual意味着”等待,直到執行時期再確定應該調用哪一個函數“,而inline卻意味着”在編譯階段,將調用動作以被調用函數的主體取代之“。如果編譯器做決定時,尚不知道該調用哪一個函數,很難責成他們做出一個inline函數。
慎用inline
內聯爲提高函數的執行效率,以代碼膨脹(複製)爲代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。 如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那麼效率的收穫會很少。另一方面,每一處內聯函數的調用都要複製代碼,將使程序的總代碼量增大,消耗更多的內存空間。因此,需要酌情使用。
以下情況不宜使用內聯:
- (1) 如果函數體內的代碼比較長,使用內聯將導致內存消耗代價較高。
- (2) 如果函數體內出現循環,那麼執行函數體內代碼的時間要比函數調用的開銷大。
- (3) 類的構造函數和析構函數容易讓人誤解成使用內聯更有效。要當心構造函數和析構函數可能會隱藏一些行爲,如“偷偷地”執行了基類或成員對象的構造函數和析構函數。所以不要隨便地將構造函數和析構函數的定義體放在類聲明中。
一個好的編譯器將會根據函數的定義體,自動地取消不值得的內聯(這進一步說明了 inline 不應該出現在函數的聲明中)。即“inline函數僅僅是一個對編譯器的建議,所以最後能否真正內聯,看編譯器的意思”,當有編譯宏明確需要使用內聯時,請使用__forceinline __attribute__((always_inline)
__forceinline
編譯器嘗試內聯函數,而不考慮函數的特徵,在某些情況下,編譯器可能會選擇忽略__forceinline,而不內聯函數。例如:
- 遞歸函數永遠不會內聯到自身中。
- 使用alloca()的函數從不內聯。
當有編譯宏(COMPLIE_ZMODULE_NAME)替換時,最好還是強制內聯以確保debug版本(不會編譯內聯函數,即使加了inline關鍵字)與release版本(可能優化爲內聯函數),從而導致COMPLIE_ZMODULE_NAME的內容存在差異引起一系列問題。
__forceinline void function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << "COMPLIE_ZMODULE_NAME:" << COMPLIE_ZMODULE_NAME << std::endl;
}
__attribute__((always_inline)
編譯器嘗試內聯函數,而不考慮函數的特徵,在某些情況下,編譯器可能會選擇忽略__attribute__((always_inline)函數屬性,而不內聯函數。例如:
- 遞歸函數永遠不會內聯到自身中。
- 使用alloca()的函數從不內聯。
當有編譯宏(COMPLIE_ZMODULE_NAME)替換時,最好還是強制內聯以確保debug版本(不會編譯內聯函數,即使加了inline關鍵字)與release版本(可能優化爲內聯函數),從而導致COMPLIE_ZMODULE_NAME的內容存在差異引起一系列問題。
inline void __attribute__((always_inline)) function_inline()
{
std::cout << __FILE__ << ":" << __LINE__ << "/" << __FUNCTION__ << "COMPLIE_ZMODULE_NAME:" << COMPLIE_ZMODULE_NAME << std::endl;
}