





Kiedy kompilator natrafia na wywołanie funkcji inline, wstawia w to miejsce kod tej funkcji [w normalnym przypadku kompilator wstawia kod przekazujący parametry funkcji i wywołujący ją - przyp. tłum.] - koncepcyjnie przypomina to rozwijanie makra #define. Pozwala to, w zależności od "zilionów" innych czynników, na zwiększenie wydajności programu, ponieważ moduł optymalizujący zaszyty w kompilatorze może proceduralnie zintegrować kod wywoływanej funkcji z kodem funkcji ją wywołującej.
Na ogół funkcję deklaruje się jako inline przy użyciu słowa kluczowego inline, ale można to zrobić bez niego. Jednakże w jakikolwiek sposób zadeklarujesz, że funkcja ma być inline, deklaracja ta jest jedynie "propozycją", którą kompilator może zignorować: kompilator rozwinie niektóre, wszystkie lub żadne z wywołań funkcji inline. (Nie rozczarowywuj się tą beznadziejną niejasnością. Elastyczność przy rozwijaniu wywołań funkcji inline, wbrew pozorom, jest potężną zaletą: pozwala ona kompilatorowi różnie traktować duże i małe funkcje, a dodatkowo zawsze możesz kazać kompilatorowi wygenerować łatwy do zdebugowania kod wynikowy, jeśli wyłączysz w nim rozwijanie funkcji inline).
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]

Rozpatrzmy poniższy kod, w którym następuje wywołanie funkcji g():
W typowej implementacji wywołania funkcji w języku C++, wykorzystującej rejestry procesora i stos, tuż przed wywołaniem funkcji g() stan rejestrów oraz parametry są zapisywane na stos; następnie funkcja g() odczytuje parametry ze stosu, i tuż przed zakończeniem swojego działania przywraca wcześniejszy stan rejestrów; później sterowanie wraca do funkcji f(). Przebieg wywołania funkcji wymaga więc kilkukrotnego zapisywania i odczytywania tych samych danych, zwłaszcza gdy kompilator umieści zmienne x, y i z w rejestrach: każda zmienna jest wtedy dwukrotnie zapisywana (jako parametr i do rejestru) oraz dwukrotnie odczytywana (gdy zostaje użyta w funkcji g() i podczas przywracania stanu rejestrów w czasie powrotu do funkcji f()).
Jeśli kompilator wstawi kod funkcji g() w miejsce jej wywołania, wszystkie te operacje na pamięci nagle znikną. Nie nastąpi zapis-do ani odczyt-z rejestrów ponieważ nie będzie wywołania funkcji, a parametry nie zostaną przesłane do/z stosu ponieważ kompilator będzie wiedział, że one są już w rejestrach procesora.
Oczywiście istnieją "ziliony" czynników, których tu nie uwzględniłem, ale powyższe może służyć za przykład jednego z wielu kroków, jakie kompilator może podjąć wtrakcie integracji proceduralnej.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]

Tak i nie. Czasem. Może.
Na to pytanie nie ma prostej odpowiedzi. Funkcje inline mogą zarówno przyśpieszyć jak i spowolnić wykonywanie kodu. Mogą też zwiększyć rozmiar kodu wynikowego lub go zmniejszyć. Wreszcie mogą być, i najczęściej są, zupełnie niezwiązane z szybkością programu.
Funkcje inline mogą przyśpieszyć program: Jak pokazano w poprzednim faqu, integracja proceduralna może wyeliminować całą masę niepotrzebnych instrukcji, które do tej pory spowalniały program.
Funkcje inline mogą spowolnić program: Zbyt duża liczba rozwinięć funkcji inline może spowodować rozrost kodu (ang. "code bloat" - spuchnięcie kodu) - kod wtedy może zająć więcej stron w pamięci, co może doprowadzić do ich "przerzucania" (ang. thrashing) przez system operacyjny obsługujący wirtualną pamięć. Inaczej mówiąc, jeżeli kod wynikowy programu będzie zbyt duży, system będzie tracił masę czasu na przesyłanie fragmentów kodu między pamięcią fizyczną a dyskiem.
Funkcje inline mogą powiększyć program: Wynika to ze wspomnianego wcześniej rozrostu kodu. Np. jeśli program zawiera 100 funkcji inline, każda z nich po skompilowaniu zajmuje 100 bajtów i jest wywoływana w 100 miejscach w programie, to spowoduje to zwiększenie rozmiaru kodu wynikowego o 1MB. Czy ten 1MB stanowi jakiś problem? Kto wie, ale zawsze jest możliwe, że ten ostatni megabajt kodu nie zmieści się w pamięci i zmusi system operacyjny do korzystania z dysku (wspomniany wcześniej "thrashing"), co spowoduje pogorszenie wydajności.
Funkcje inline mogą zmniejszyć program: W przypadku bardzo krótkich funkcji kompilator często generuje więcej kodu maszynowego mającego odczytywać/zapisywać parametry i rejestry na stos, niż mającego wykonać instrukcje zawarte w tej funkcji. Zdarza się to również przy dużych funkcjach, kiedy optymalizator może usunąć sporą ilość zbędnego kodu poprzez integrację proceduralną i tym samym zmniejszyć tę funkcję do niewielkich rozmiarów.
Funkcje inline mogą przyczynić się do częstego "przerzucania" stron między pamięcią a twardym dyskiem: Stosowanie funkcji inline może zwiększyć rozmiar kodu wynikowego i w efekcie doprowadzić do "przerzucania" stron [większy rozmiar kodu zwiększa prawdopodobieństwo, że nie zmieści się on w pamięci fizycznej i jego fragmenty będą często przesyłane między dyskiem a RAMem - przyp. tłum.]
Funkcje inline mogą zapobiec "przerzucaniu" stron między pamięcią a dyskiem: Po zastosowaniu funkcji inline zbiór roboczy programu (ang. "working set" - liczba stron, która musi się w danej chwili znajdować w RAMie) może ulec zmniejszeniu nawet, jeżeli wzrósł rozmiar kodu wynikowego. Jako przykład rozpatrzmy dwie funkcje: f() i g(); w pewnym miejscu pierwsza z nich wywołuje drugą. W "normalnym" przypadku (obie funkcje nie są inline), kompilator może rozmieścić je na osobnych stronach [w takim przypadku zawsze istnieje jakieś prawdopodobieństwo, że jedna z tych stron będzie się znajdować na dysku - wtedy wywołanie funkcji g() lub powrót do funkcji f() wymagać będzie przesłania tej strony do RAMu - przyp. tłum.]. Natomiast jeżeli zdefiniujemy te funkcje jako inline, kompilator będzie mógł zintegrować proceduralnie wywołanie funkcji g() wewnątrz funkcji f() - w takim przypadku najczęściej obie funkcje znajdą się na jednej stronie [i nie będzie konieczne pobieranie "tej drugiej" z dysku - przyp. tłum.].
Funkcje inline mogą w ogóle nie wpłynąć na szybkość : W większości systemów komputerowych głównym czynnikiem ograniczającym wydajność nie jest wcale szybkość działania procesora, ale głównie wolna komunikacja z urządzeniami, bazami danych, siecią. Z wyjątkiem sytuacji, gdy procesor jest obciążony w 100%, "przesiadka" na funkcje inline prawdopodobnie nie przyśpieszy działania programu. (Nawet w systemach, w których właśnie szybkość procesora jest głównym czynnikiem determinującym wydajność, zastosowanie funkcji inline pomaga jedynie w "wąskich gardłach" programu, które na ogół występują w nieznacznej części kodu.)
Nie ma prostych odpowiedzi: Musisz sam trochę poeksperymentować, żeby stwierdzić co jest najlepsze w danym przypadku. Nie opieraj się na uproszczonych sądach w stylu "Nigdy nie używaj funkcji inline" lub "Zawsze używaj funkcji inline" lub "Używaj funkcji inline wtedy i tylko wtedy, gdy kod funkcji zajmuje mniej niż N linijek". Tego rodzaju zasady-na-każdą-okazję są może i łatwe do zapisania, ale ich rezultaty są najwyżej suboptymalne.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]

W języku C, strukturę z enkapsulacją można było uzyskać przez umieszczenie w niej wskaźnika void* wskazującego do danych, które miały pozostać ukrytymi dla użytkowników struktury. Programista mógł nie wiedzieć w jaki sposób interpretować dane wskazywane przez void*a, ale funkcje operujące na strukturze rzutowały void*a na wskaźnik odpowiedniego typu (którego definicja była ukryta gdzieś w kodzie programu). Dawało to pewien rodzaj enkapsulacji.
Niestety, stosując tę metodę traciło się zalety kontroli typu. Ponadto wymuszała ona wywoływanie funkcji aby uzyskać dostęp nawet do najbardziej trywialnych składowych struktury (jeśli zezwolonoby na bezpośredni dostęp do wszystkich jej składowych, użytkownik z dowolnego miejsca w programie w dowolnym momencie mógłby mieć dostęp do nich, gdyż z konieczności musiałby wiedzieć jak interpretować dane wskazywane przez "void*a"; to z kolei utrudniałoby wprowadzanie późniejszych zmian do struktury).
Wywołanie funkcji może stanowić niewielki koszt, jednak zawsze jakiś. Funkcje zaszyte wewnątrz klas w C++ mogą być rozwijane w miejscu wywołania dzięki słowu kluczowemu inline. Dzięki temu możliwe jest jednoczesne osiągnięcie bezpieczeństwa związanego z enkapsulacją oraz szybkości wynikającej z bezpośredniego dostępu i braku wywołania funkcji. Warto tu też dodać, że - podobnie jak w przypadku zwykłych funkcji - tak i w funkcjach inline kompilator sprawdza, czy przekazane parametry są odpowiednich typów (co stanowi ulepszenie w stosunku do makr "define").
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]

Ponieważ makra #define są złe, i to aż poczwórnie: złe#1, złe#2, złe#3, i złe#4. Czasem ich użycie jest konieczne i można zrezygnować z tej zasady, ale one i tak wciąż będą "złe".
W odróżnieniu od makr #define, funkcje inline pozwalają uniknąć starych, znanych błędów związanych z użyciem makr, ponieważ funkcje tylko raz obliczają każde wyrażenie przekazane im jako parametr. Inaczej mówiąc, wywołanie funkcji inline niczym się nie różni od wywołania zwykłej funkcji, poza tym że jest szybsze:
Oprócz tego, parametry przekazywane funkcji inline są sprawdzane pod kątem zgodności typów, a ewentualne niejawne rzutowania są prawidłowo wykonywane.
Makra są niedobre dla Twojego zdrowia; lepiej ich nie używaj chyba, że nie ma innej możliwości.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Kiedy deklarujesz funkcję inline, wygląda ona tak samo jak zwykła funkcja:
Jednak gdy definiujesz tę funkcję, dopisujesz przed nią słowo kluczowe inline, a całą definicję umieszczasz w pliku nagłówkowym.
Uwaga: Konieczne jest, aby definicja funkcji inline znajdowała się w pliku nagłówkowym, chyba że funkcja ta jest wykorzystywana tylko w jednym pliku .cpp. W przeciwnym wypadku, jeżeli w jednym pliku .cpp będzie się znajdować definicja funkcji, a w drugim jej wywołanie to przy generowaniu kodu wynikowego programu linker (konsolidator) zgłosi błąd "nieprawidłowe wiązanie zewnętrzne" (ang. "unresolved external").
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Deklaracja metody inline wygląda tak jak deklaracja każdej innej metody:
Jednakże definicja metody inline różni się od definicji zwykłej metody tym, że poprzedza ją słowo kluczowe inline. Ponadto definicja metody inline powinna być umieszczona w pliku nagłówkowym.
Zazwyczaj konieczne jest, aby definicja metody inline znajdowała się w pliku nagłówkowym. Jeśli byłaby ona umieszczona w jednym pliku .cpp, a w innym pliku znajdowałoby się jej wywołanie to wtrakcie generowania kodu wynikowego programu linker zgłosiłby błąd "nieprawidłowe wiązanie zewnętrzne" (ang. "unresolved external").
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Owszem: można zdefiniować metodę w obrębie definicji klasy:
Mimo że ten sposób wydaje się być prostszy dla kogoś implementującego klasę, na ogół utrudnia on zrozumienie działania klasy osobom czytającym później ten kod, z powodu wymieszania ze sobą informacji o tym "co" klasa robi i "jak" to robi. Z tego powodu metody przeważnie się definiuje poza obszarem definicji klasy używając słowa kluczowego inline. Pogląd ten nabiera sensu przy następującym spojrzeniu: wielu ludzi może korzystać z Twojej klasy, ale jest tylko jeden człowiek, który ją zaprojektował (ty, rzecz jasna); wobec tego powinieneś podejmować decyzje korzystniejsze raczej dla większości, niż dla mniejszości.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
E-mail the author
[ C++ FAQ Lite
| Spis treści
| Skorowidz
| O autorze
| ©
| Pobierz swoją własną kopię ]
Ostatnia aktualizacja Jun 17, 2002
Wersja polska: 0.1i Jul 12, 2004