Share via


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, Deposita 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, __inlinea __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í __forceinlinebuď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 /clrkompilá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 __forceinlinev uvedeném pořadí, pokud není zadána možnost/Zakompilá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, /O1nebo /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žijte inline_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šíří na 2 + 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é

noinline
auto_inline