內嵌函式 (C++)
關鍵詞 inline
建議編譯程式取代函式定義內的程式碼,以取代該函式的每個呼叫。
理論上,使用內嵌函式可以讓您的程式更快,因為它們可以消除與函式呼叫相關聯的額外負荷。 呼叫函式需要推送堆疊上的傳回位址、將自變數推送至堆棧、跳至函式主體,然後在函式完成時執行傳回指令。 內嵌函式可消除此程式。 編譯程式也有不同的機會,可將擴充的函式與非內嵌函式優化。 內嵌函式的取捨是程序的整體大小可能會增加。
內嵌程式代碼替代是在編譯程式的任意時間完成。 例如,如果採用編譯程式位址,或編譯程式決定函式太大,則編譯程式不會內嵌函式。
類別宣告主體中定義的函式隱含為內嵌函式。
範例
在下列類別宣告中,建 Account
構函式是內嵌函式,因為它定義在類別宣告的主體中。 成員函式 GetBalance
、 Deposit
和 Withdraw
會在其定義中指定 inline
。 關鍵詞 inline
在類別宣告的函式宣告中是選擇性的。
// account.h
class Account
{
public:
Account(double initial_balance)
{
balance = initial_balance;
}
double GetBalance() const;
double Deposit(double amount);
double Withdraw(double amount);
private:
double balance;
};
inline double Account::GetBalance() const
{
return balance;
}
inline double Account::Deposit(double amount)
{
balance += amount;
return balance;
}
inline double Account::Withdraw(double amount)
{
balance -= amount;
return balance;
}
注意
在類別宣告中,未使用 inline
關鍵詞宣告函式。 關鍵詞 inline
可以在類別宣告中指定;結果相同。
指定的內嵌成員函式在每個編譯單位中必須以相同方式宣告。 內嵌函式必須只有一個定義。
除非該函式的定義包含 inline
規範,否則類別成員函式預設為外部連結。 上述範例顯示您不需要使用規範明確 inline
宣告這些函式。 在 inline
函式定義中使用,會建議編譯程式將它視為內嵌函式。 不過,您無法在 inline
呼叫該函式之後重新宣告函式。
inline
、__inline
和 __forceinline
和 inline
__inline
規範會建議編譯程式在呼叫函式的每個位置插入函式主體的複本。
只有在編譯程式自己的成本效益分析顯示其價值時,才會發生插入稱為 內嵌擴充 或 內嵌。 內嵌擴充會以較大的程式代碼大小的潛在成本將函式呼叫額外負荷降至最低。
__forceinline
關鍵詞會覆寫成本效益分析,並改為依賴程序設計人員的判斷。 使用 __forceinline
時請小心。 不分青紅皂白地使用 __forceinline
可能會導致較大的程式代碼只獲得臨界效能,或在某些情況下,甚至是效能損失(例如,因為較大型可執行檔的分頁增加)。
編譯器會將內嵌展開選項和關鍵字視為建議, 不保證函式會內嵌。 您無法強制編譯程式內嵌特定函式,即使使用 關鍵詞也一樣 __forceinline
。 當您使用 /clr
編譯 時,如果已套用至函式的安全性屬性,編譯程式將不會內嵌函式。
為了與舊版相容,_inline
且 分別是 和__forceinline
的同義字__inline
,除非指定編譯程式選項 /Za
(停用語言延伸模組)。_forceinline
inline
關鍵詞會告知編譯程式內嵌擴充是慣用的。 不過,編譯程式可以忽略它。 發生此行為的兩個案例如下:
- 遞迴函式
- 透過在轉譯單位其他地方的指標所參考的函式。
這些原因可能會干擾內嵌, 如同編譯程式所決定的其他原因一樣。 不要相依於 inline
規範導致函式內嵌。
編譯程式可以在多個轉譯單位中建立內嵌函式,而不是展開頭文件中定義的內嵌函式。 編譯程式會標記連結器產生的函式,以防止發生單一定義規則 (ODR) 違規。
如同一般函式,內嵌函式中沒有自變數評估的定義順序。 事實上,當使用一般函數調用通訊協議傳遞時,它可能會與自變數評估順序不同。
使用編譯程式 /Ob
優化選項來影響內嵌函式展開是否確實發生。
/LTCG
會進行跨模組內嵌,不論是否在原始程式碼中要求。
範例 1
// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
return a < b ? b : a;
}
類別的成員函式可以使用 關鍵詞或將函式定義放在類別定義內,以內嵌 inline
方式宣告。
範例 2
// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>
class MyClass
{
public:
void print() { std::cout << i; } // Implicitly inline
private:
int i;
};
Microsoft 特定
關鍵字 __inline
相當於 inline
。
即使使用 __forceinline
,如果下列專案,編譯程式也無法內嵌函式:
- 函式或其呼叫端會使用
/Ob0
編譯 (偵錯組建的預設選項)。 - 函式和呼叫端使用不同的例外狀況處理 (一個使用 C++ 例外狀況處理,另一個使用結構化例外狀況處理)。
- 函式具有變數引數清單。
- 除非使用、
/O1
或/O2
編譯/Ox
,否則函式會使用內嵌元件。 - 函式是遞歸的,而且沒有
#pragma inline_recursion(on)
設定。 伴隨 pragma,遞迴函式內嵌至預設為 16 個呼叫的深度。 若要減少內嵌深度,請使用inline_depth
pragma。 - 函式是虛擬的,也是以虛擬方式呼叫。 對虛擬函式的直接呼叫可以內嵌。
- 程式使用函式的位址,而且此呼叫是透過函式指標進行的。 其位址已取用之函式的直接呼叫可以內嵌。
- 函式也會以
naked
__declspec
修飾詞標示。
如果編譯程式無法內嵌使用 __forceinline
宣告的函式,則會產生層級 1 警告,但下列狀況除外:
- 函式是使用 /Od 或 /Ob0 編譯。 在這些情況下,不會有內嵌。
- 函式是在外部定義、在內含連結庫或其他轉譯單位中,或是虛擬呼叫目標或間接呼叫目標。 編譯程式無法識別目前翻譯單元中找不到的非內嵌程序代碼。
遞歸函式可以用內嵌程序代碼取代為 pragma 所 inline_depth
指定的深度,最多 16 個呼叫。 該深度之後,遞迴函式呼叫視為函式執行個體的呼叫。 內嵌啟發學習法檢查遞歸函式的深度不能超過 16。 inline_recursion
pragma 會控制目前展開中函式的內嵌擴充。 如需相關信息,請參閱 Inline-Function Expansion (/Ob) 編譯程式選項。
END Microsoft 特定的
如需使用 inline
規範的詳細資訊,請參閱:
何時使用內嵌函式
內嵌函式最適合用於小型函式,例如提供數據成員存取權的函式。 簡短函式會區分函數調用的額外負荷。 較長的函式在呼叫和傳回序列中花費的時間會比較少,而且受益於內嵌。
Point
類別的定義如下:
// when_to_use_inline_functions.cpp
// compile with: /c
class Point
{
public:
// Define "accessor" functions
// as reference types.
unsigned& x();
unsigned& y();
private:
unsigned _x;
unsigned _y;
};
inline unsigned& Point::x()
{
return _x;
}
inline unsigned& Point::y()
{
return _y;
}
假設座標操作是這類類別客戶端中相對常見的作業,請指定這兩個存取子函式(x
並在 y
上述範例中),通常 inline
可節省下列專案的額外負荷:
- 函式呼叫 (包括參數傳遞以及將物件的位址放置在堆疊上)
- 保留呼叫端的堆疊框架
- 新的堆疊框架設定
- 傳回值通訊
- 還原舊的堆疊框架
- 傳回
內嵌函式與宏
宏與函式有一 inline
些共同之處。 但有重要的差異。 請考慮下列範例:
#include <iostream>
#define mult1(a, b) a * b
#define mult2(a, b) (a) * (b)
#define mult3(a, b) ((a) * (b))
inline int multiply(int a, int b)
{
return a * b;
}
int main()
{
std::cout << (48 / mult1(2 + 2, 3 + 3)) << std::endl; // outputs 33
std::cout << (48 / mult2(2 + 2, 3 + 3)) << std::endl; // outputs 72
std::cout << (48 / mult3(2 + 2, 3 + 3)) << std::endl; // outputs 2
std::cout << (48 / multiply(2 + 2, 3 + 3)) << std::endl; // outputs 2
std::cout << mult3(2, 2.2) << std::endl; // no warning
std::cout << multiply(2, 2.2); // Warning C4244 'argument': conversion from 'double' to 'int', possible loss of data
}
33
72
2
2
4.4
4
以下是宏與內嵌函式之間的一些差異:
- 宏一律會內嵌展開。 不過,只有在編譯程序判斷它是最佳動作時,才會內嵌函數。
- 宏可能會導致非預期的行為,這可能會導致細微的錯誤。 例如,表達式
mult1(2 + 2, 3 + 3)
會展開2 + 2 * 3 + 3
評估為 11,但預期的結果為 24。 看似有效的修正是在函式宏的這兩個自變數周圍加上括號,導致#define mult2(a, b) (a) * (b)
,這樣會解決手邊的問題,但在較大表達式的一部分時,仍會造成令人驚訝的行為。 這在上述範例中已示範,而且問題可藉由定義 宏來解決,例如#define mult3(a, b) ((a) * (b))
。 - 內嵌函式受限於編譯程式的語意處理,而預處理器則會擴充宏,而沒有相同優點。 宏不是類型安全,而函式則為 。
- 做為引數傳遞至內嵌函式的運算式會評估一次。 在某些情況下,做為引數傳遞至巨集的運算式可以多次評估。 例如,請考慮下列事項:
#include <iostream>
#define sqr(a) ((a) * (a))
int increment(int& number)
{
return number++;
}
inline int square(int a)
{
return a * a;
}
int main()
{
int c = 5;
std::cout << sqr(increment(c)) << std::endl; // outputs 30
std::cout << c << std::endl; // outputs 7
c = 5;
std::cout << square(increment(c)) << std::endl; // outputs 25
std::cout << c; // outputs 6
}
30
7
25
6
在此範例中 increment
,當表達式 sqr(increment(c))
展開至 ((increment(c)) * (increment(c)))
時,會呼叫 函式兩次。 這導致 的第二次調用 increment
傳回 6,因此表達式評估為 30。 任何包含副作用的表達式在宏中使用時都可能會影響結果,請檢查完全展開的宏,以檢查行為是否預期。 相反地,如果使用內嵌函 square
式, increment
則只會呼叫函式一次,並取得 25 的正確結果。
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應