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 GetBalance
składowe , Deposit
i 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
, __inline
i __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ą /clr
polecenia 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 __forceinline
programu 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
,/O1
lub/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żyjinline_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ą __forceinline
polecenia , 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:
- Wbudowane funkcje składowe klasy
- Definiowanie funkcji śródwierszowych języka C++ z dllexport i dllimport
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ść, do2 + 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ż
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla