Funkcje wbudowane (C++)

Słowo inline kluczowe sugeruje, że kompilator zastępuje kod w definicji funkcji zamiast każdego wywołania tej funkcji.

Teoretycznie użycie funkcji wbudowanych może przyspieszyć działanie programu, ponieważ eliminuje obciążenie związane z wywołaniami funkcji. Wywołanie funkcji wymaga wypychania adresu zwrotnego na stos, wypychania argumentów do stosu, przechodzenia do treści funkcji, a następnie wykonywania instrukcji zwracanej po zakończeniu działania funkcji. Ten proces został wyeliminowany przez podkreślenie funkcji. Kompilator ma również różne możliwości optymalizacji rozszerzonych funkcji wbudowanych w porównaniu z tymi, które nie są. Kompromis funkcji wbudowanych polega na tym, że ogólny rozmiar programu może wzrosnąć.

Podstawianie kodu wbudowanego odbywa się według uznania kompilatora. Na przykład kompilator nie będzie w tekście funkcji, jeśli jej adres zostanie podjęty lub jeśli kompilator zdecyduje, że jest zbyt duży.

Funkcja zdefiniowana w treści deklaracji klasy jest niejawnie funkcją śródliniową.

Przykład

W następującej deklaracji klasy konstruktor jest funkcją śródliniową, Account ponieważ jest zdefiniowany w treści deklaracji klasy. Funkcje GetBalanceskładowe , Depositi Withdraw są określone inline w ich definicjach. Słowo inline kluczowe jest opcjonalne w deklaracjach funkcji w deklaracji klasy.

// 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;
}

Uwaga

W deklaracji klasy funkcje zostały zadeklarowane bez słowa kluczowego inline . Słowo inline kluczowe można określić w deklaracji klasy. Wynik jest taki sam.

Dana funkcja składowa wbudowanych musi być zadeklarowana w taki sam sposób w każdej jednostce kompilacji. Musi istnieć dokładnie jedna definicja funkcji wbudowanej.

Funkcja składowa klasy domyślnie łączy zewnętrznych, chyba że definicja tej funkcji zawiera inline specyfikator. W poprzednim przykładzie pokazano, że nie trzeba jawnie deklarować tych funkcji za pomocą specyfikatora inline . Użycie inline w definicji funkcji sugeruje kompilatorowi, że jest traktowany jako funkcja śródliniowa. Nie można jednak ponownie zadeklarować funkcji jako inline po wywołaniu tej funkcji.

inline, __inlinei __forceinline

Specyfikatory inline i __inline sugerują kompilatorowi, że wstawia kopię treści funkcji do każdego miejsca wywoływana jest funkcja.

Wstawianie, nazywane rozszerzeniem wbudowanym lub tworzeniem, występuje tylko wtedy, gdy analiza kosztów i korzyści kompilatora pokazuje, że warto. Rozszerzanie wbudowane minimalizuje obciążenie wywołania funkcji kosztem większego rozmiaru kodu.

Słowo __forceinline kluczowe zastępuje analizę kosztów i korzysta z oceny programisty. Zachowaj ostrożność podczas korzystania z programu __forceinline. Masowe użycie elementu __forceinline może spowodować zwiększenie ilości kodu tylko z nieznacznymi wzrostami wydajności lub, w niektórych przypadkach, nawet stratami wydajności (ze względu na zwiększone stronicowanie większego pliku wykonywalnego, na przykład).

Kompilator traktuje opcje rozszerzenia wbudowanego i słowa kluczowe jako sugestie. Nie ma gwarancji, że funkcje zostaną podkreślone. Nie można wymusić, aby kompilator wymyślił konkretną funkcję, nawet za pomocą słowa kluczowego __forceinline . Podczas kompilowania za pomocą /clrpolecenia kompilator nie będzie wbudowany funkcji, jeśli do funkcji zastosowano atrybuty zabezpieczeń.

Aby uzyskać zgodność z poprzednimi wersjami _inline i _forceinline są synonimami odpowiednio i __inline__forceinline, chyba że określono opcję /Za kompilatora (Wyłącz rozszerzenia języka).

Słowo inline kluczowe informuje kompilator, że preferowane jest rozszerzenie wbudowane. Jednak kompilator może go zignorować. Dwa przypadki, w których takie zachowanie może się zdarzyć, to:

  • Funkcje rekursywne.
  • Funkcje, do których odwołuje się wskaźnik w innym miejscu lekcji tłumaczenia.

Te przyczyny mogą zakłócać tworzenie inlin, podobnie jak inne osoby określone przez kompilator. Nie należy zależeć od specyfikatora inline , aby spowodować, że funkcja będzie wiśnięta.

Zamiast rozszerzać funkcję śródliniową zdefiniowaną w pliku nagłówkowym, kompilator może utworzyć ją jako funkcję wywoływaną w więcej niż jednej jednostce tłumaczenia. Kompilator oznacza wygenerowaną funkcję konsolidatora, aby zapobiec naruszeniom reguł jednej definicji (ODR).

Podobnie jak w przypadku normalnych funkcji, nie ma zdefiniowanej kolejności obliczania argumentów w funkcji wbudowanej. W rzeczywistości może to być inne niż kolejność oceny argumentów po przekazaniu przy użyciu normalnego protokołu wywołania funkcji.

Użyj opcji optymalizacji kompilatora /Ob , aby określić, czy rozszerzenie funkcji wbudowanej rzeczywiście występuje.
/LTCG Program tworzy między modułami inlining, niezależnie od tego, czy jest on żądany w kodzie źródłowym, czy nie.

Przykład 1

// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
    return a < b ? b : a;
}

Funkcje składowe klasy można zadeklarować w tekście, używając słowa kluczowego inline lub umieszczając definicję funkcji w definicji klasy.

Przykład 2

// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>

class MyClass
{
public:
    void print() { std::cout << i; }   // Implicitly inline

private:
    int i;
};

Specyficzne dla firmy Microsoft

Słowo __inline kluczowe jest równoważne .inline

Nawet w przypadku __forceinlineprogramu kompilator nie może w tekście funkcji, jeśli:

  • Funkcja lub jej obiekt wywołujący jest kompilowany za pomocą /Ob0 polecenia (opcja domyślna dla kompilacji debugowania).
  • Funkcja i obiekt wywołujący używają różnych typów obsługi wyjątków (obsługa wyjątków w języku C++ w jednej, ustrukturyzowanej obsługi wyjątków w drugiej).
  • Funkcja ma listę argumentów zmiennych.
  • Funkcja używa wbudowanego zestawu, chyba że skompilowany z elementem /Ox, /O1lub /O2.
  • Funkcja jest rekursywna i nie ma #pragma inline_recursion(on) ustawionej wartości . Dzięki pragma funkcje rekursywne są podkreślone do domyślnej głębokości 16 wywołań. Aby zmniejszyć głębokość podkreślenia, użyj inline_depth pragma.
  • Funkcja jest wirtualna i jest wywoływana praktycznie. Bezpośrednie wywołania funkcji wirtualnych mogą być wbudowane.
  • Program pobiera adres funkcji, a wywołanie jest wykonywane za pośrednictwem wskaźnika do funkcji. Bezpośrednie wywołania funkcji, które miały swój adres, mogą być podkreślone.
  • Funkcja jest również oznaczona modyfikatorem naked__declspec .

Jeśli kompilator nie może w tekście zadeklarować funkcji za pomocą __forceinlinepolecenia , generuje ostrzeżenie poziomu 1, z wyjątkiem sytuacji, gdy:

  • Funkcja jest kompilowana przy użyciu /Od lub /Ob0. W takich przypadkach nie jest oczekiwane żadne podkreślenie.
  • Funkcja jest definiowana zewnętrznie w dołączonej bibliotece lub innej jednostce tłumaczenia albo jest elementem docelowym wywołania wirtualnego lub obiektu docelowego wywołania pośredniego. Kompilator nie może zidentyfikować niezwiązanego kodu, którego nie może znaleźć w bieżącej lekcji tłumaczenia.

Funkcje cykliczne można zastąpić wbudowanym kodem na głębokość określoną przez inline_depth pragma, maksymalnie 16 wywołań. Po tej głębokości wywołania funkcji cyklicznych są traktowane jako wywołania do wystąpienia funkcji. Głębokość, do której funkcje rekursywne są badane przez heurystyczną śródliniową, nie może przekraczać 16. Pragma inline_recursion steruje rozszerzaniem wbudowanej funkcji obecnie w trakcie rozszerzania. Aby uzyskać powiązane informacje, zobacz opcję kompilatora wbudowanej funkcji (/Ob).

END Microsoft Specific

Aby uzyskać więcej informacji na temat używania specyfikatora inline , zobacz:

Kiedy należy używać funkcji wbudowanych

Funkcje wbudowane najlepiej nadają się do obsługi małych funkcji, takich jak te, które zapewniają dostęp do składowych danych. Krótkie funkcje są wrażliwe na obciążenie wywołań funkcji. Dłuższe funkcje spędzają proporcjonalnie mniej czasu w wywoływaniu i zwracaniu sekwencji i korzystają z mniejszej korzyści z tworzenia podkreślenia.

Klasę Point można zdefiniować w następujący sposób:

// 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;
}

Zakładając, że manipulowanie współrzędnymi jest stosunkowo powszechną operacją w kliencie takiej klasy, określając dwie funkcje dostępu (x i y w poprzednim przykładzie), ponieważ inline zwykle zapisuje obciążenie:

  • Wywołania funkcji (w tym przekazywanie parametrów i umieszczanie adresu obiektu na stosie)
  • Zachowywanie ramki stosu obiektu wywołującego
  • Nowa konfiguracja ramki stosu
  • Komunikacja wartości zwracanej
  • Przywracanie starej ramki stosu
  • Powrót

Funkcje wbudowane a makra

Makro ma pewne elementy wspólne z funkcją inline . Ale istnieją ważne różnice. Rozważmy następujący przykład:

#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

Poniżej przedstawiono niektóre różnice między makrem a funkcją śródliniową:

  • Makra są zawsze rozszerzane w tekście. Jednak funkcja śródwierszowa jest wbudowana tylko wtedy, gdy kompilator określa, że jest to optymalna rzecz do zrobienia.
  • Makro może spowodować nieoczekiwane zachowanie, co może prowadzić do drobnych usterek. Na przykład wyrażenie mult1(2 + 2, 3 + 3) rozszerza wartość, do 2 + 2 * 3 + 3 której daje wartość 11, ale oczekiwany wynik wynosi 24. Pozornie prawidłową poprawką jest dodanie nawiasów wokół obu argumentów makra funkcji, co spowoduje #define mult2(a, b) (a) * (b)rozwiązanie problemu, ale nadal może powodować zaskakujące zachowanie w przypadku większego wyrażenia. Pokazano to w poprzednim przykładzie, a problem można rozwiązać, definiując makro jako takie #define mult3(a, b) ((a) * (b)).
  • Funkcja śródliniowa podlega przetwarzaniu semantycznemu przez kompilator, natomiast preprocesor rozszerza makra bez tej samej korzyści. Makra nie są bezpieczne dla typów, podczas gdy funkcje są.
  • Wyrażenia przekazywane jako argumenty do funkcji wbudowanych są obliczane raz. W niektórych przypadkach wyrażenia przekazywane jako argumenty do makr można ocenić więcej niż raz. Rozważmy na przykład następujące kwestie:
#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

W tym przykładzie funkcja increment jest wywoływana dwa razy, gdy wyrażenie sqr(increment(c)) rozszerza się na ((increment(c)) * (increment(c))). Spowodowało to drugie wywołanie increment funkcji zwracania wartości 6, dlatego wyrażenie daje wartość 30. Każde wyrażenie zawierające skutki uboczne może mieć wpływ na wynik w przypadku użycia w makrze, zbadaj w pełni rozwinięte makro, aby sprawdzić, czy zachowanie jest zamierzone. Zamiast tego, jeśli użyto funkcji square wbudowanej, funkcja increment będzie wywoływana tylko raz, a prawidłowy wynik 25 zostanie uzyskany.

Zobacz też

noinline
auto_inline