Inline функції вельми цікавий рудимент, дістався сучасному світові з уже далеких бандитських 90-х. Коли процвітав асемблер, Сі компілював дуже компактні і маленькі програми, коли процесори були слабенькими (в порівнянні з тим, що зараз в мобілки ставлять наприклад) і час виконання коду цінувалося на вагу золота. Цей тип функцій взагалі-то використовувався не тільки в Сі, і в той лихий час виправдовував себе.
Я вважаю, не для кого не секрет, що стародавні комп'ютери вимагали при написанні ПЗ для важких обчислень досить таки серйозного підходу з позиції програміста. Викручуватися і економити доводилося на всьому, інакше час роботи програми збільшувалася в рази. Це зараз ми ганяємо гігабайтні іграшки не особливо скаржачись на швидкість роботи. У той час це було надзвичайно критично, і одним із способів скоротити час роботи, як раз були в лінію (встроенные) функции. Зараз я спробую більш менш доступно розповісти чому.
Итак, що з себе представляє звичайна функція? Візьмемо наприклад простий приклад – обчислення факторіала.
1 2 3 4 5 6 7 8 | int factorial() { int i = 0; int result = 1; for(i = 2;i < 5; i++) result *= i; return result; } |
Досить прості обчислення факторіала (5!) в цикле for, і повернення результату з функції. С ++ розцінює цю функцію як якийсь блок операцій, згрупований в окремий блок. Блок цей після компіляції поміщається в осередку пам'яті одного разу в коді, і тіло функції (цикл в даному випадку) ніде більше в компільованою програмі не повторюється. Все гарно – виходить якийсь ділянку пам'яті, належав програмі, на який процесор при необхідності перескакує з того місця, де знаходить виклик.
1 | cout << factorial(); |
В даному операторі ця функція задіяна, процесор просто виконає команду асемблера CALL, в якій буде переданий адреса функції. Тобто,. виклик функції обійдеться в один оператор (якщо грубо говорити). При этом, в памяти (точніше кажучи в стеці програми) займається місце для параметрів функції, якщо вони є, і обов'язково для адреси, звідки процесор стрибнув на функцію.
В лінію функція позбавляє процесор стрибати в клітинку, за адресою якої починається ця функція. сам сенс в лінію полягає в тому, щоб замість виклику функції підставити її тіло (код функції) в місце, де вона викликається.
Якщо описати наш факторіал так:
1 2 3 4 5 6 7 8 | inline int factorial() { int i = 0; int result = 1; for(i = 2; i < 5; i++) result *= i; return result; } |
то замість
1 | cout << factorial(); |
ми отримаємо розворот функції в набір операторів:
1 2 3 4 5 | int i = 0; result = 1; for(i = 2; i < 5; i++) result *= i; cout << result; |
як нібито самі в коді написали в цьому місці.
Відповідно код типу:
1 | cout << factorial() << factorial() / 5; |
перетвориться в
1 2 3 4 5 6 7 8 9 | int i = 0; result = 1; for(i = 2; i < 5; i++) result *= i; cout << result; result = 1; for(i = 2; i < 5; i++) result *= i cout << result / 5; |
Якщо міряти філософськи – кількість коду з в лінію функцією збільшилася. Замість одного рядка виклику функції її тіло, підставлену замість виклику , дало цілих 6 строк. Так само буде і в компільованою програмі – кількість операторів зросте багаторазово – на стільки, скільки операторів в тілі функції і скільки разів її вписали в програму.
Тобто,. відмінність між inline функцією і звичайною функцією – дублювання коду тіла функції всюди, де вона виявляється задіяна. У звичайній функції, її тіло знаходиться в єдиному екземплярі в одному і тому ж місці всередині програми.
Де тут вигода запитаєте ви? Економиться час процесора на стрибки з місця виклику в тіло функції. Якщо функція величезна і використовується в декількох місцях, те в лінію виходить не зовсім вигідно. Однак, якщо функція (тіло її) маленьке, з мінімальною кількістю операторів, вирішують завдання, за старих часів було зручніше відмовитися від стрибка і просто підставити їх в потрібне місце, як ніби сам програміст там їх описав.
Як би там не було, в побутових умовах та ще й на сучасних комп'ютерах, программы, використовують в лінію підстановку тіла функції замість виклику, не дають особливих переваг. Тому використовувати цей вид функції доводиться досить рідко. ІМХО, йому місце в музеї слави. хоча, щоб бути до кінця чесним, такий підхід дає свої плоди тим, хто програмує контролери, процесори та інші залізяки. Але там свої особливості і свій підхід, і в це заглиблюватися зараз не варто. використовувати вбудовану функцію (в лінію функцію) чи ні – вирішувати самому програмісту. Від себе можу додати тільки одне – не варто робити це там, де цього не потрібно за завданням.
> Тому використовувати цей вид функції доводиться досить рідко.
Використовувати inline функції явно доводиться використовувати, возможно, і не так часто. Але вони масово використовуються неявно при визначенні класів і створення об'єктів:
– будь-яка функція-метод, визначення котрой знаходиться всередині визначення її класу, неявно отримує кваліфікатор inline.
На прикладі класу, визначального 2D-точку (на площині):
class point {private:
float x, y;
public:
...
float operator -( const point& p ) { // расстояние 2-х 2D
float dx = ( x - p.x ), dy = ( y - p.y );
return sqrtf( dx * dx + dy * dy );
}
};
І альтернативне 2-е визначення, дуже схоже:
class point {private:
float x, y;
public:
...
float operator -( const point& p ); // расстояние 2-х 2D
};
float point::operator -( const point& p ) {
float dx = ( x - p.x ), dy = ( y - p.y );
return sqrtf( dx * dx + dy * dy );
};
У 1-му варіанті відстань між 2-ма точками буде всюди обчислюватися inline, а в другому – викликом функції-методу.
Звичайно код в коментарях відображається – це жах … але, сподіваюся, буде зрозуміло.
Це вже турбота компілятора. Мається на увазі самому програмісту рідко доводиться в побутових умовах використовувати цей вид функцій.
Нет. Це як раз той випадок, коли компілятору заборонено втручатися в реалізацію методу: якщо метод описаний всередині класу – в лінію, а якщо всередині метод тільки оголошується, а реалізація або поза оголошення класу або в окремому файлі (що частіше) – функцімональний виклик.
Це як-раз випадок, коли реалізація знаходиться повністю під контролем автора, а автор повинен знати чим його визначення обернуться.
Принцип інлайн не змінюється в залежності від того, в ООП його застосовують або в функціоналки )
До чого такі ускладнення теорії? Я не проти уточнень, але тоді потрібна ще одна стаття, описує різницю між тим і цим випадками. Немає ніякої потреби заважати всю теорію в купу – вийде той же самий MSDN.
ще однією “дрібницею” є те, що кваліфікатор inline є рекомендаційним: компілятор використовує його лише тоді, коли це можливо, якщо він вважає, що це неприпустимо, він використовує функціональний виклик без всякого на те укедомленя (навіть попередженням).
Приклад – визначте функцію факторіала так:
inline unsigned long long fact3( int n ) {return 1 == n ? 1 : n * fact3( n - 1 );
}
Тут inline не зіграє ніякої ролі.
Ну то ж саме. У сучасному Сі компілятор сам вирішує. У своєму DLYA 8086 или 80286 не всі компілятори так оптимізували.
Ще одна “дрібниця”: функції з кваліфікаторів inline чудово працюють, але до тих пір, поки визначення функції і виклик знаходяться в одному файлі коду. При зростанні проекту, варто розділити код на кілька файлів (роздільна компіляія з подальшим зв'язуванням об'єктних файлів), і це створить величезні проблеми з пошуком помилки: inline функціонально непридатні для зовнішнього зв'язування.
в лінію – це визначник макроозначення, і вони непридатні для зовнішнього зв'язування (пов'язаний).
> Цей тип функцій взагалі-то використовувався не тільки в Сі, і в той лихий час виправдовував себе.
У стандарті мови C взагалі немає ключового слова inline, там подібні речі реалізуються через параметризовані макроси #define.
У деяких пізніх компіляторах C inline включено, але тільки для пддержанія совместімсоті з C ++.
Тому в принципі невірно говорити, що inline запозичене з C, це – чисто C ++ придбання.
Тому я б ось те “хвацькі” початок викинув.
А я не натякав на стандарт. Я натякав на МВР. У Паскалі теж такі функції існували. Та й в бейсике здається було щось подібне в пізніх його видах.
> Цей тип функцій взагалі-то використовувався не тільки в Сі
У мові C не було ключового слова inline від моменту, коли його в 1969 р. почав формулювати Денис Рітчі, і майже до 2000-х років.
Весь цей час якщо слів inline з'явилося програмі C, воно б викликало синтаксичну помилку.
До питання сумісності C і C ++ … щодо inline:
Якщо компілювати код як C, але з ключем -std = c89 (т.е. ANSI стандарт 1989р.), то на inline просто буде синтаксична помилка. І на всіх попередніх стандартах мови C.
Використання inline в C допустить тільки компілятор з опцією -std = c99, т.е. таке ключове слово допускається стандартом C тільки після 1999р.
Взагалі то, стаття про inline функції, на відміну від більшості статей сайту – невдала. Мені здається, що її слід було б злегка скорегувати, додавши і виправивши з урахуванням зазначених позицій.
Я, коли її писав, не бажав вдаватися в особливі подробиці, щоб не загороджувати ними саму суть. Не дарма в інтернеті є правило “многабукаф”, яке часто роздмухує контент в салат Ой-Лівьйо.