C++ udostępnia funkcje wbudowane w celu zmniejszenia narzutu związanego z wywoływaniem funkcji. Funkcja wbudowana to funkcja, która jest rozwijana w linii podczas jej wywoływania. Gdy wywoływana jest funkcja inline, cały kod funkcji inline jest wstawiany lub zastępowany w miejscu wywołania funkcji inline. To podstawienie jest wykonywane przez kompilator C++ w czasie kompilacji. Funkcja wbudowana może zwiększyć wydajność, jeśli jest mała.
Składnia:
inline return-type function-name(parameters) { // function code }>
Pamiętaj, że wstawianie jest tylko żądaniem skierowanym do kompilatora, a nie poleceniem. Kompilator może zignorować żądanie wstawienia.
Kompilator nie może wykonać inline w takich okolicznościach jak:
- Jeśli funkcja zawiera pętlę. ( na, podczas gdy i do-while )
- Jeśli funkcja zawiera zmienne statyczne.
- Jeśli funkcja jest rekurencyjna.
- Jeśli typ zwracany przez funkcję jest inny niż void, a instrukcja return nie istnieje w treści funkcji.
- Jeśli funkcja zawiera instrukcję switch lub goto.
Dlaczego używane są funkcje wbudowane?
Kiedy program wykonuje instrukcję wywołania funkcji, CPU przechowuje adres pamięci instrukcji następującej po wywołaniu funkcji, kopiuje argumenty funkcji na stos i na koniec przekazuje kontrolę określonej funkcji. Następnie procesor wykonuje kod funkcji, przechowuje wartość zwracaną przez funkcję w predefiniowanym miejscu/rejestrze pamięci i zwraca kontrolę do funkcji wywołującej. Może to stanowić narzut, jeśli czas wykonania funkcji jest krótszy niż czas przełączania z funkcji wywołującej do funkcji wywoływanej (odbiorcy wywoływanego).
W przypadku funkcji, które są duże i/lub wykonują złożone zadania, obciążenie wywołania funkcji jest zwykle nieistotne w porównaniu z ilością czasu potrzebnego na uruchomienie funkcji. Jednak w przypadku małych, często używanych funkcji czas potrzebny na wywołanie funkcji jest często znacznie dłuższy niż czas potrzebny do faktycznego wykonania kodu funkcji. Ten narzut występuje w przypadku małych funkcji, ponieważ czas wykonania małej funkcji jest krótszy niż czas przełączania.
Funkcje wbudowane Zalety:
- Narzut wywołania funkcji nie występuje.
- Oszczędza to również narzut związany ze zmiennymi push/pop na stosie, gdy wywoływana jest funkcja.
- Oszczędza to również narzut związany z wywołaniem zwrotnym z funkcji.
- Kiedy wstawiasz funkcję, możesz umożliwić kompilatorowi wykonanie optymalizacji kontekstowej w treści funkcji. Takie optymalizacje nie są możliwe w przypadku normalnych wywołań funkcji. Inne optymalizacje można uzyskać, biorąc pod uwagę przepływy kontekstu wywołującego i kontekstu wywoływanego.
- Funkcja wbudowana może być użyteczna (jeśli jest mała) w systemach wbudowanych, ponieważ funkcja wbudowana może dać mniej kodu niż funkcja zwana preambułą i powrotem.
Funkcja inline Wady:
- Dodane zmienne z funkcji inline zużywają dodatkowe rejestry. Po zastosowaniu funkcji inline, jeśli liczba zmiennych, która będzie używać rejestru, wzrośnie, mogą one spowodować narzut związany z wykorzystaniem zasobów zmiennych rejestru. Oznacza to, że gdy w miejscu wywołania funkcji zostanie podstawiona treść funkcji wbudowanej, wstawiana zostanie również całkowita liczba zmiennych używanych przez funkcję. Zatem liczba rejestrów używanych dla zmiennych również ulegnie zwiększeniu. Jeśli więc po wpisaniu funkcji liczby zmiennych drastycznie wzrosną, z pewnością spowoduje to obciążenie rejestru.
- Jeśli użyjesz zbyt wielu funkcji wbudowanych, rozmiar binarnego pliku wykonywalnego będzie duży z powodu powielenia tego samego kodu.
- Zbyt duże wstawianie może również zmniejszyć współczynnik trafień w pamięci podręcznej instrukcji, zmniejszając w ten sposób prędkość pobierania instrukcji z pamięci podręcznej do pamięci podstawowej.
- Funkcja inline może wydłużyć czas kompilacji, jeśli ktoś zmieni kod wewnątrz funkcji inline, wówczas cała lokalizacja wywołująca będzie musiała zostać ponownie skompilowana, ponieważ kompilator będzie musiał ponownie zamienić cały kod, aby odzwierciedlić zmiany, w przeciwnym razie będzie kontynuował starą funkcjonalność.
- Funkcje wbudowane mogą nie być przydatne w wielu systemach wbudowanych. Ponieważ w systemach wbudowanych rozmiar kodu jest ważniejszy niż szybkość.
- Funkcje wbudowane mogą powodować szarpanie, ponieważ wstawianie może zwiększyć rozmiar binarnego pliku wykonywalnego. Zaśmiecanie pamięci powoduje spadek wydajności komputera. Poniższy program demonstruje użycie funkcji inline.
Przykład:
C++
#include> using> namespace> std;> inline> int> cube(>int> s) {>return> s * s * s; }> int> main()> {> >cout <<>'The cube of 3 is: '> << cube(3) <<>'
'>;> >return> 0;> }> |
>
>Wyjście
The cube of 3 is: 27>
Funkcja i klasy wbudowane
Możliwe jest również zdefiniowanie funkcji inline wewnątrz klasy. W rzeczywistości wszystkie funkcje zdefiniowane wewnątrz klasy są domyślnie wbudowane. Dlatego też tutaj stosowane są wszystkie ograniczenia funkcji inline. Jeśli chcesz jawnie zadeklarować funkcję inline w klasie, po prostu zadeklaruj funkcję wewnątrz klasy i zdefiniuj ją poza klasą za pomocą słowa kluczowego inline.
Składnia:
class S { public: inline int square(int s) // redundant use of inline { // this function is automatically inline // function body } };> Powyższy styl jest uważany za zły styl programowania. Najlepszym stylem programowania jest po prostu napisanie prototypu funkcji wewnątrz klasy i określenie go jako wbudowanego w definicji funkcji.
Na przykład:
class S { public: int square(int s); // declare the function }; inline int S::square(int s) // use inline prefix { }> Przykład:
C++
// C++ Program to demonstrate inline functions and classes> #include> using> namespace> std;> class> operation {> >int> a, b, add, sub, mul;> >float> div>;> public>:> >void> get();> >void> sum();> >void> difference();> >void> product();> >void> division();> };> inline> void> operation ::get()> {> >cout <<>'Enter first value:'>;> >cin>> a;> >cout <<>'Enter second value:'>;> >cin>>b;> }> inline> void> operation ::sum()> {> >add = a + b;> >cout <<>'Addition of two numbers: '> << a + b <<>'
'>;> }> inline> void> operation ::difference()> {> >sub = a - b;> >cout <<>'Difference of two numbers: '> << a - b <<>'
'>;> }> inline> void> operation ::product()> {> >mul = a * b;> >cout <<>'Product of two numbers: '> << a * b <<>'
'>;> }> inline> void> operation ::division()> {> >div> = a / b;> >cout <<>'Division of two numbers: '> << a / b <<>'
'>;> }> int> main()> {> >cout <<>'Program using inline function
'>;> >operation s;> >s.get();> >s.sum();> >s.difference();> >s.product();> >s.division();> >return> 0;> }> |
>
jak odzyskać ukryte aplikacje
>
Wyjście:
Enter first value: 45 Enter second value: 15 Addition of two numbers: 60 Difference of two numbers: 30 Product of two numbers: 675 Division of two numbers: 3>
Co jest nie tak z makro?
Czytelnicy zaznajomieni z językiem C wiedzą, że język C wykorzystuje makro. Preprocesor zastępuje wszystkie wywołania makr bezpośrednio w kodzie makra. Zaleca się, aby zawsze używać funkcji wbudowanej zamiast makra. Według dr Bjarne Stroustrupa twórcy makr C++ prawie nigdy nie są w C++ potrzebni i są podatni na błędy. Istnieją pewne problemy z użyciem makr w C++. Makro nie może uzyskać dostępu do prywatnych członków klasy. Makra wyglądają jak wywołania funkcji, ale w rzeczywistości nimi nie są.
Przykład:
C++
// C++ Program to demonstrate working of macro> #include> using> namespace> std;> class> S {> >int> m;> public>:> >// error> #define MAC(S::m)> };> |
>
>
Wyjście:
Error: '::' may not appear in macro parameter list #define MAC(S::m)>
Kompilator C++ sprawdza typy argumentów funkcji inline i czy niezbędne konwersje są wykonywane poprawnie. Makro preprocesora nie jest w stanie tego zrobić. Kolejną rzeczą jest to, że makrami zarządza preprocesor, a funkcjami wbudowanymi zarządza kompilator C++. Pamiętaj: Prawdą jest, że wszystkie funkcje zdefiniowane wewnątrz klasy są domyślnie inline i kompilator C++ wykona inline wywołania tych funkcji, ale kompilator C++ nie może wykonać inline, jeśli funkcja jest wirtualna. Powód wywołania funkcji wirtualnej jest rozwiązywany w czasie wykonywania, a nie w czasie kompilacji. Wirtualne oznacza czekanie do czasu wykonania, a inline oznacza podczas kompilacji, jeśli kompilator nie wie, która funkcja zostanie wywołana, w jaki sposób może wykonać inline? Kolejną rzeczą do zapamiętania jest to, że wbudowanie funkcji ma sens tylko wtedy, gdy czas spędzony na wywołaniu funkcji jest dłuższy w porównaniu z czasem wykonania treści funkcji.
Przykład, w którym funkcja inline nie ma żadnego efektu:
inline void show() { cout << 'value of S = ' << S << endl; }> Wykonanie powyższej funkcji zajmuje stosunkowo dużo czasu. Ogólnie rzecz biorąc, funkcji wykonującej operację wejścia-wyjścia (I/O) nie należy definiować jako wbudowanej, ponieważ zajmuje ona znaczną ilość czasu. Technicznie rzecz biorąc, wstawienie funkcji show() ma ograniczoną wartość, ponieważ ilość czasu, jaką zajmie wykonanie instrukcji I/O, znacznie przekracza obciążenie wywołania funkcji. W zależności od używanego kompilatora, kompilator może wyświetlić ostrzeżenie, jeśli funkcja nie zostanie rozwinięta w linii.
Języki programowania, takie jak Java i C#, nie obsługują funkcji wbudowanych. Jednak w Javie kompilator może wykonać wstawianie, gdy wywoływana jest metoda small final, ponieważ metod final nie można zastąpić podklasami, a wywołanie metody final jest rozstrzygane w czasie kompilacji.
W C# kompilator JIT może również optymalizować kod, wstawiając małe wywołania funkcji (np. zastępując treść małej funkcji, gdy jest ona wywoływana w pętli). Ostatnią rzeczą, o której należy pamiętać, jest to, że funkcje wbudowane są cenną cechą C++. Właściwe użycie funkcji inline może zapewnić poprawę wydajności, ale jeśli funkcje inline zostaną użyte arbitralnie, nie zapewnią lepszych wyników. Innymi słowy, nie należy oczekiwać lepszej wydajności programu. Nie umieszczaj każdej funkcji w linii. Lepiej jest, aby funkcje inline były jak najmniejsze.