11.10 Funkcje rozwijane

Funkcje, zarówno globalne, jak i, o czym się przekonamy w późniejszych rozdziałach, będące składowymi klas, mogą być rozwijane (ang. inlined). O takich funkcjach mówi się też funkcje otwarte lub wklejane. Rozwijanie oznacza, że kod (instrukcje maszynowe) funkcji jest przez kompilator umieszczany w każdym miejscu pliku wykonywalnego, gdzie funkcja powinna, według treści programu, być wywołana. Zatem tak naprawdę taka funkcja w ogóle nie będzie wywoływana i, co więcej, w ogóle w kodzie wynikowym jako osobna funkcja nie będzie istnieć.

Gdybyśmy na przykład napisali trywialną funkcję typu

       int fun(int m) {
           return 2*m;
       }
to korzyść z niej byłaby wątpliwa: wywołanie funkcji wiąże się z zapamiętaniem aktualnego stanu rejestrów, wykonaniem skoku, powrotu itd.; jest to operacja dość kosztowna. Program może być nieco dłuższy, ale wykonywać się będzie szybciej, jeśli mnożenie przez dwa umieścimy bezpośrednio w kodzie w każdym miejscu, gdzie tę funkcję wołaliśmy, a wywołania funkcji i samą funkcję w ogóle usuniemy. Z drugiej strony, umieszczanie takiego samego czy bardzo podobnego fragmentu kodu w wielu miejscach programu mogłoby być niezwykle uciążliwe, a co gorsza, czyniłoby ten program zupełnie nieczytelnym. Strach pomyśleć, co by było, gdybyśmy taki zastępujący wywołanie funkcji fragment musieli zmodyfikować: trzeba by to robić w każdym miejscu uważając na ewentualne konflikty nazw i kontekst, w jakim ten fragment pojawia się w różnych częściach programu. Aby to sobie ułatwić, możemy po prostu zadanie to zlecić kompilatorowi. W naszym programie definiujemy funkcję z modyfikatorem inline, na przykład
       inline int fun(int m) {
           return 2*m;
       }
Modyfikator inline mówi, że kompilator nie powinien tej funkcji kompilować tak jak innych funkcji, ale zastąpić każde jej wywołanie fragmentem kodu odpowiadającym jej treści. Wykonywalny plik wynikowy może być nieco dłuższy, ale wykonywać się będzie szybciej; łatwiej też będzie zmienić mnożenie przez dwa na mnożenie przez trzy — w tekście programu zrobimy to w jednym miejscu, a kompilator zadba o to, by w kodzie wykonywalnym zmiana zaszła we wszystkich miejscach gdzie tej funkcji użyliśmy.

W miejscu, gdzie następuje w programie wywołanie funkcji rozwijanej, musi być już znana definicja funkcji!

Definicja, a nie tylko deklaracja. Ponieważ kompilator musi zastąpić wywołanie funkcji jej treścią, sama deklaracja nie wystarczy. Pociąga to pewną niekonsekwencję w stosunku do tego, co mówiliśmy wcześniej: normalne funkcje, czyli funkcje nierozwijane, a więc zamknięte, deklarować można wiele razy, ale definiować tylko raz. W przypadku funkcji rozwijanych definicja (taka sama) musi istnieć w każdym module, gdzie funkcja jest używana, i to leksykalnie przed miejscem tego użycia. Zwykle najwygodniej jest zatem umieścić tę definicję w pliku nagłówkowym i włączać na początku każdego modułu, gdzie funkcja ma być użyta.

Pamiętać jednak trzeba, że dodanie do definicji modyfikatora inline nie gwarantuje, że funkcja rzeczywiście zostanie rozwinięta. Po prostu, jest to często dla kompilatora za trudne; jeśli istnieje prawdopodobieństwo błędu lub zniekształcenia możliwych intencji programisty — których przecież kompilator dokładnie nie zna — to funkcja nie będzie rozwijana: zostanie skompilowana jako normalna funkcja, czyli funkcja zamknięta. To, czy dana funkcja zdefiniowana z modyfikatorem inline będzie czy nie będzie rozwinięta, zależy w dużym stopniu od użytego kompilatora. Niektóre kompilatory informują o tym podczas kompilacji (choć większość tego nie robi).

Zwykle funkcja nie będzie rozwinięta, jeśli zawiera skomplikowane instrukcje warunkowe, pętle, szczególnie z „ifami” w środku, wywołania rekurencyjne itd.

Jako rozwijane (zadeklarowane jako inline) definiuje się najczęściej krótkie, proste funkcje, ale takie, które wywoływane są wielokrotnie, bo na przykład używane są wewnątrz mocno zagnieżdżonej pętli. W zasadzie zazwyczaj nie rozwijamy funkcji aż do końcowego etapu pisania programu: wtedy dopiero sprawdzamy (za pomocą profilowania), które funkcje „zjadają” najwięcej czasu i te właśnie funkcje rozwijamy (obserwując, czy daje to jakiś zauważalny pozytywny efekt).

T.R. Werner, 23 lutego 2019; 23:59