Vložené funkce (C++)
Klíčové inline
slovo naznačuje, že kompilátor nahradí kód v definici funkce místo každého volání této funkce.
Teoreticky může použití vložených funkcí urychlit váš program, protože eliminují režii spojenou s voláními funkcí. Volání funkce vyžaduje nasdílení návratové adresy do zásobníku, nasdílení argumentů do zásobníku, přechod do těla funkce a následné provedení návratové instrukce po dokončení funkce. Tento proces je eliminován vložením funkce. Kompilátor má také různé příležitosti k optimalizaci funkcí rozšířených přímo v porovnání s funkcemi, které nejsou. Kompromisem vložených funkcí je, že celková velikost programu se může zvýšit.
Nahrazení vloženého kódu se provádí podle uvážení kompilátoru. Kompilátor například nenasadí funkci, pokud se její adresa převezme nebo pokud se kompilátor rozhodne, že je příliš velký.
Funkce definovaná v těle deklarace třídy je implicitně vloženou funkcí.
Příklad
V následující deklaraci třídy je konstruktor vloženou funkcí, Account
protože je definován v těle deklarace třídy. Členské funkce GetBalance
, Deposit
a Withdraw
jsou uvedeny inline
v jejich definicích. Klíčové inline
slovo je volitelné v deklarací funkce v deklaraci třídy.
// 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;
}
Poznámka:
V deklaraci třídy byly funkce deklarovány bez klíčového inline
slova. Klíčové inline
slovo lze zadat v deklaraci třídy; výsledek je stejný.
Jedna vložená členská funkce musí být deklarována ve všech jednotkách kompilace stejně. Musí existovat přesně jedna definice vložené funkce.
Členová funkce třídy se ve výchozím nastavení nastaví na externí propojení, pokud definice této funkce neobsahuje inline
specifikátor. Předchozí příklad ukazuje, že tyto funkce nemusíte explicitně deklarovat pomocí specifikátoru inline
. Použití inline
v definici funkce navrhuje kompilátoru, že se považuje za vloženou funkci. Funkci ale nemůžete předefinovat jako inline
po volání této funkce.
inline
, __inline
a __forceinline
__inline
Specifikátory inline
a specifikátory navrhují kompilátoru, že vloží kopii těla funkce do každého místa, kde je funkce volána.
Vložení, označované jako vložené rozšíření nebo vkládání, nastane pouze v případě, že analýza vlastního nákladového přínosu kompilátoru ukazuje, že je to užitečné. Vložené rozšíření minimalizuje režii volání funkce s potenciálními náklady na větší velikost kódu.
Klíčové __forceinline
slovo přepíše analýzu nákladů a přínosů a spoléhá na úsudek programátora. Při použití __forceinline
buďte opatrní . Nerozlišující použití může vést k většímu __forceinline
kódu pouze s mezním zvýšením výkonu nebo v některých případech i ztrátou výkonu (kvůli zvýšenému stránkování většího spustitelného souboru, například).
Kompilátor zpracovává možnosti vloženého rozšíření a klíčová slova jako návrhy. Neexistuje žádná záruka, že funkce budou vloženy. Kompilátor nemůže vynutit vložení konkrétní funkce, a to ani s klíčovým slovem __forceinline
. Při kompilaci s /clr
kompilátorem nebude vložena funkce, pokud jsou na funkci použity atributy zabezpečení.
Kvůli kompatibilitě s předchozími verzemi _inline
a _forceinline
jsou synonymy pro __inline
a __forceinline
v uvedeném pořadí, pokud není zadána možnost/Za
kompilátoru (Zakázat jazyková rozšíření).
Klíčové inline
slovo říká kompilátoru, že je upřednostňované vložené rozšíření. Kompilátor ho ale může ignorovat. K tomuto chování může dojít ve dvou případech:
- Rekurzivní funkce.
- Funkce, které jsou v jednotce překladu označovány prostřednictvím ukazatele jinam.
Tyto důvody mohou kolidovat s vkládáním, stejně jako ostatní, jak je určeno kompilátorem. Nespoléhejte na specifikátoru inline
, aby funkce byla vložena.
Místo rozšíření vložené funkce definované v souboru záhlaví může kompilátor vytvořit jako volatelnou funkci ve více než jedné jednotce překladu. Kompilátor označí vygenerovanou funkci linkeru, aby se zabránilo porušení pravidla odr (one-definition-rule).
Stejně jako u normálníchfunkcích Ve skutečnosti se může lišit od pořadí vyhodnocení argumentu při předání pomocí normálního protokolu volání funkce.
Pomocí možnosti optimalizace kompilátoru /Ob
můžete ovlivnit, jestli se skutečně vyskytuje rozšíření vložené funkce.
/LTCG
inlinuje křížový modul bez ohledu na to, jestli je požadován ve zdrojovém kódu, nebo ne.
Příklad 1
// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
return a < b ? b : a;
}
Členské funkce třídy lze deklarovat v textu buď pomocí klíčového inline
slova, nebo umístěním definice funkce do definice třídy.
Příklad 2
// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>
class MyClass
{
public:
void print() { std::cout << i; } // Implicitly inline
private:
int i;
};
Specifické pro Microsoft
Klíčové __inline
slovo je ekvivalentní inline
.
I v případě __forceinline
, že kompilátor nemůže vložit funkci, pokud:
- Funkce nebo její volající se zkompiluje (
/Ob0
výchozí možnost pro sestavení ladění). - Funkce a volající používají různé druhy zpracování výjimek (zpracování výjimek jazyka C++ ve funkci, strukturované zpracování výjimek u volajícího).
- Funkce má proměnný seznam argumentů.
- Funkce používá vložené sestavení, pokud není zkompilováno s
/Ox
,/O1
nebo/O2
. - Funkce je rekurzivní a nemá
#pragma inline_recursion(on)
nastavenou hodnotu. Pomocí direktivy pragma jsou rekurzivní funkce vloženy do výchozí hloubky 16 volání. Pokud chcete snížit hloubku vkládání, použijteinline_depth
direktivu pragma. - Funkce je virtuální a je volána virtuálně. Přímá volání virtuálních funkcí mohou být vložená.
- Program přebere adresu funkce a volání se provádí prostřednictvím ukazatele na funkci. Přímá volání funkcí, jejichž adresa byla odebrána, mohou být vložená.
- Funkce je také označena modifikátorem
naked
__declspec
.
Pokud kompilátor nemůže vložit funkci deklarovanou s __forceinline
, vygeneruje upozornění úrovně 1 s výjimkou případů:
- Funkce je zkompilována pomocí /Od nebo /Ob0. V těchto případech se neočekává žádné vkládání.
- Funkce je definována externě, v zahrnuté knihovně nebo jiné jednotce překladu nebo je cílem virtuálního volání nebo nepřímý cíl volání. Kompilátor nemůže identifikovat nevlojený kód, který nemůže najít v aktuální jednotce překladu.
Rekurzivní funkce lze nahradit vloženým kódem do hloubky určené inline_depth
direktivou pragma až do maximálního počtu 16 volání. Následně jsou hluboké rekurzivní funkce považovány za volání instance funkce. Hloubka, do které se rekurzivní funkce prověřují vloženou heuriskou, nesmí překročit 16. Direktiva inline_recursion
pragma řídí vložené rozšíření funkce, která se právě rozšiřuje. Související informace najdete v možnosti kompilátoru inline-Function Expansion (/Ob).
END Microsoft Specific
Další informace o použití specifikátoru najdete v inline
tématu:
Kdy použít vložené funkce
Vložené funkce se nejlépe používají pro malé funkce, jako jsou ty, které poskytují přístup k datovým členům. Krátké funkce jsou citlivé na režii volání funkcí. Delší funkce tráví v volání a vracené sekvenci úměrně méně času a těží z vkládání.
Třídu Point
lze definovat takto:
// 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;
}
Za předpokladu, že manipulace se souřadnicemi je poměrně běžná operace v klientovi takové třídy, zadejte dvě funkce přístupového objektu (x
a y
v předchozím příkladu), jak inline
obvykle šetří režijní náklady na:
- Volání funkcí (včetně předávání parametrů a ukládání adresy objektu do zásobníku)
- Zachování rámce zásobníku volajícího
- Nové nastavení rámce zásobníku
- Komunikace návratové hodnoty
- Obnovení starého rámce zásobníku
- Zpět
Vložené funkce vs. makra
Makro má několik věcí společného s inline
funkcí. Existují ale důležité rozdíly. Představte si následující příklad:
#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
Tady jsou některé rozdíly mezi makrem a vloženou funkcí:
- Makra se vždy rozbalí jako vložená. Vložená funkce je však vložena pouze v případě, že kompilátor určí, že je optimální.
- Makro může vést k neočekávanému chování, což může vést k drobným chybám. Výraz se například
mult1(2 + 2, 3 + 3)
rozšíří na2 + 2 * 3 + 3
hodnotu 11, ale očekávaný výsledek je 24. Zdánlivě platnou opravou je přidat závorky kolem obou argumentů makra funkce, což způsobí#define mult2(a, b) (a) * (b)
, že problém vyřeší, ale může stále způsobit překvapivý chování, když je součástí většího výrazu. To bylo demonstrováno v předchozím příkladu a problém by mohl být vyřešen definováním makra, jako je například#define mult3(a, b) ((a) * (b))
. - Vložená funkce podléhá sémantickému zpracování kompilátorem, zatímco preprocesor rozšiřuje makra bez této výhody. Makra nejsou typově bezpečná, zatímco funkce jsou.
- Výrazy předané jako argumenty vložených funkcí jsou vyhodnoceny jednou. V některých případech mohou být výrazy, které jsou předány jako argumenty makrům, vyhodnoceny více než jednou. Představte si například následující:
#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
V tomto příkladu se funkce increment
volá dvakrát, protože výraz sqr(increment(c))
se rozšiřuje na ((increment(c)) * (increment(c)))
. To způsobilo druhé vyvolání návratu increment
6, proto se výraz vyhodnotí jako 30. Jakýkoli výraz, který obsahuje vedlejší účinky, může ovlivnit výsledek při použití v makre, prozkoumejte plně rozbalené makro a zkontrolujte, jestli je chování zamýšlené. Místo toho, pokud byla vložená funkce square
použita, bude funkce increment
volána pouze jednou a správný výsledek 25 se získá.
Viz také
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro