Коли я повинен написати ключове слово 'inline' для функції / методу?


562

Коли я повинен написати ключове слово inlineдля функції / методу в C ++?

Побачивши кілька відповідей, кілька пов’язаних питань:

  • Коли я не повинен писати ключове слово 'inline' для функції / методу в C ++?

  • Коли компілятор не знатиме, коли зробити функцію / метод 'inline'?

  • Чи має значення додаток багатопотокове, коли пишете "inline" для функції / методу?


40
Якщо ви визначите функцію в заголовку, вам потрібно буде оголосити її вбудованою. В іншому випадку ви отримаєте помилки лінкера щодо декількох визначень функції.
Мартін Йорк

15
@Martin: Якщо це не у визначенні класу, бути вибагливим.
Девід Торнлі

20
@David: Щоб бути дуже вибагливим, це лише тому, що такі функції неявно позначені inline(9.3 / 2).
Гонки легкості на орбіті


Також дивіться вбудовані функції в C ++ FAQ. У них дуже хороша обробка інлайн.
jww

Відповіді:


882

О людино, один із моїх домашніх вихованців.

inlineбільше схожий staticабо externчим директиви , які говорять компілятор вбудовувати свої функції. extern, static, inlineЄ директиви зчеплення, використовується майже виключно линкер, а НЕ компілятор.

Кажуть, що inlineнатякає компілятору, що ви вважаєте, що функція повинна бути вписана. Це може бути правдою в 1998 році, але через десятиліття компілятору таких підказів не потрібно. Не кажучи вже про людей, як правило, помиляються, коли йдеться про оптимізацію коду, тому більшість компіляторів відкидають ігнорують «підказку».

  • static- ім'я змінної / функції не можна використовувати в інших одиницях перекладу. Linker повинен переконатися, що він випадково не використовує статично визначену змінну / функцію з іншого блоку перекладу.

  • extern- використовувати цю змінну / ім'я функції у цьому блоці перекладу, але не скаржиться, якщо вона не визначена. Лінкер розібрає його і переконається, що весь код, який намагався використовувати якийсь символ зовнішнього вигляду, має свою адресу.

  • inline- ця функція буде визначена в декількох одиницях перекладу, не турбуйтеся про це. Лінкер повинен переконатися, що всі блоки перекладу використовують один екземпляр змінної / функції.

Примітка: Взагалі, оголошення шаблонів inlineє безглуздим, оскільки вони вже мають семантику зв’язків inline. Однак явна спеціалізація та інстанціювання шаблонів потребуєinline використання.


Конкретні відповіді на ваші запитання:

  • Коли слід написати ключове слово "inline" для функції / методу в C ++?

    Тільки тоді, коли ви хочете, щоб функція була визначена в заголовку. Точніше лише тоді, коли визначення функції може відображатися в декількох одиницях перекладу. Непогано визначити невеликі (як в одному вкладиші) функції у файлі заголовка, оскільки це дає компілятору більше інформації, з якою можна працювати, оптимізуючи ваш код. Це також збільшує час складання.

  • Коли я не повинен писати ключове слово 'inline' для функції / методу в C ++?

    Не додайте вбудований текст лише тому, що ви думаєте, що ваш код запуститься швидше, якщо компілятор вкладе його.

  • Коли компілятор не знатиме, коли зробити функцію / метод 'inline'?

    Як правило, компілятор зможе зробити це краще, ніж ви. Однак у компілятора немає можливості вбудовувати код, якщо він не має визначення функції. У максимально оптимізованому коді зазвичай усі privateспособи вкладаються, будь ласка, запитуєте його чи ні.

    Використовуйте __attribute__(( noinline ))та використовуйте Visual Studio, щоб уникнути вставки в GCC __declspec(noinline).

  • Чи має значення додаток багатопотокове, коли пишете "inline" для функції / методу?

    Багатопотокове читання жодним чином не впливає на вставки.


172
+1 Найкращий опис inline, який я бачив у ... (назавжди). Зараз я зірву вас і використаю це у всіх моїх поясненнях вбудованого ключового слова.
Мартін Йорк

6
@Ziggy, те, що я намагався сказати, це те, що вбудований компілятор і inlineключове слово не пов'язані. Хоча ти маєш правильну ідею. Як правило, здогадки про те, що було б покращено за допомогою вставки, дуже схильні до помилок. Виняток із цього правила - це один лайнер.
deft_code

4
Ця відповідь мене трохи бентежить. Ви говорите все про те, що компілятор може вбудовувати / не вбудовувати речі краще. Тоді ви говорите, що слід покласти один заголовок / малі функції у заголовок, і що компілятор не може вбудовувати код без визначення функції. Хіба це трохи не суперечать? Чому б просто не помістити все у файл cpp і дозволити компілятору вирішити?
користувач673679

5
Компілятор буде вбудовувати лише виклики функцій, якщо визначення доступне на сайті виклику. Залишивши всю функцію у файлі cpp обмеживши вкладення цього файлу. Я пропоную визначити невеликі вкладиші, вбудовані в .h, оскільки вартість швидкості компіляції незначна, і ви майже гарантовано, що компілятор вбуде вхідний виклик. Моя думка щодо вбудовування компілятора полягає в тому, що це порт чорного мистецтва оптимізації, в якому ваш компілятор набагато кращий, ніж ви.
deft_code

8
Кожного разу, коли я щось читаю на увазі накопичених знань в Інтернеті, я мушу замислюватися про відому цитату Джона Лоутона: Іронія ігрової епохи полягає в тому, що вона дала нову респектабельність необізнаній думці.
ІІнеочікувана

60

Я хотів би долучитися до всіх чудових відповідей у ​​цій темі переконливим прикладом, щоб розігнати будь-які залишилися непорозуміння.

Дано два вихідні файли, такі як:

  • inline111.cpp:

    #include <iostream>
    
    void bar();
    
    inline int fun() {
      return 111;
    }
    
    int main() {
      std::cout << "inline111: fun() = " << fun() << ", &fun = " << (void*) &fun;
      bar();
    }
  • inline222.cpp:

    #include <iostream>
    
    inline int fun() {
      return 222;
    }
    
    void bar() {
      std::cout << "inline222: fun() = " << fun() << ", &fun = " << (void*) &fun;
    }

  • Випадок A:

    Збірка :

    g++ -std=c++11 inline111.cpp inline222.cpp

    Вихід :

    inline111: fun() = 111, &fun = 0x4029a0
    inline222: fun() = 111, &fun = 0x4029a0

    Обговорення :

    1. Навіть ви повинні мати однакові визначення своїх вбудованих функцій, компілятор C ++ не позначає його, якщо це не так (насправді, через окрему компіляцію немає можливості перевірити це). Це ваш власний обов’язок забезпечити це!

    2. Лінкер не скаржиться на правило одного визначення , як fun()це оголошено як inline. Однак, оскільки inline111.cpp - це перший блок перекладу (який фактично викликає fun()), оброблений компілятором, компілятор створює екземпляр fun()при першій зустрічі виклику в inline111.cpp . Якщо компілятор вирішив не поширювати fun()свій виклик з будь-якого іншого місця у вашій програмі ( наприклад, з inline222.cpp ), виклик до fun()завжди буде пов'язаний з його екземпляром, що виробляється з inline111.cpp (виклик fun()всередині inline222.cppможе також створити екземпляр у цьому блоці перекладу, але він залишиться без зв’язку). Дійсно, це видно з однакових &fun = 0x4029a0роздруківків.

    3. Нарешті, незважаючи на inlineпропозицію компілятора фактично розширити однолінійку fun(), він повністю ігнорує вашу пропозицію, що зрозуміло, оскільки fun() = 111в обох рядках.


  • Випадок B:

    Компілюйте (зворотній порядок повідомлення) :

    g++ -std=c++11 inline222.cpp inline111.cpp

    Вихід :

    inline111: fun() = 222, &fun = 0x402980
    inline222: fun() = 222, &fun = 0x402980

    Обговорення :

    1. Цей випадок стверджує , що вже обговорювалося в разі А .

    2. Зауважте важливий момент, що якщо ви коментуєте фактичний дзвінок fun()в inline222.cpp ( напр., Коментар-повідомлення coutв inline222.cpp повністю), то, незважаючи на порядок складання ваших підрозділів перекладу, fun()буде створено миттєву інформацію при першій зустрічі дзвінка у inline111.cpp , що призводить до роздруківки для Case B як inline111: fun() = 111, &fun = 0x402980.


  • Справа C:

    Складіть (повідомлення -O2) :

    g++ -std=c++11 -O2 inline222.cpp inline111.cpp

    або

    g++ -std=c++11 -O2 inline111.cpp inline222.cpp

    Вихід :

    inline111: fun() = 111, &fun = 0x402900
    inline222: fun() = 222, &fun = 0x402900

    Обговорення :

    1. Як описано тут , -O2оптимізація закликає компілятор фактично розширити функції , які можуть бути вбудованими (Зауважте також , що -fno-inlineце за замовчуванням без параметрів оптимізації). Як видно з цього відбитка, fun()фактично було розширено вбудований текст (згідно з його визначенням у цій конкретній одиниці перекладу), що призвело до двох різних fun() роздруківків. Незважаючи на це, все ще існує лише один глобально пов'язаний екземпляр fun()(як того вимагає стандарт), як це видно з ідентичної &fun роздруківки.

8
Ваша відповідь є ілюстративним повідомленням про те, чому мова робить такі inlineфункції невизначеною поведінкою.
R Саху

Вам також слід додати випадки, коли компіляція та посилання є окремими, кожен з яких .cppє власною одиницею перекладу. Переважно, додайте регістри для -fltoвключених / вимкнених.
syockit

Посилання на C ++ прямо говорить: "Якщо функція вбудованої чи змінної (оскільки C ++ 17) із зовнішнім зв'язком визначена по-різному в різних одиницях перекладу, поведінка не визначена." Отже, написаний вами матеріал є специфічним для GCC, оскільки це побічний ефект від упорядкування процесів компіляції та зв’язків. Також зауважте, що це може відрізнятися між версіями.
Петро Фідлер

27

Вам все одно потрібно чітко вбудувати свою функцію під час спеціалізації шаблонів (якщо спеціалізація знаходиться у .h файлі)


21

1) У наш час майже ніколи. Якщо вбудувати функцію добре, компілятор зробить це без вашої допомоги.

2) Завжди. Див. №1.

(Відредаговано так, що ви розбили своє питання на два запитання ...)


Так. Вбудований текст - це лише натяк на компілятор, і він може вас ігнорувати. У наші дні компілятор, напевно, знає краще, ніж програміст, які функції найкраще вбудувати.
Марк Байєрс

1
Так, але це менш актуально - для функції, яка має бути вбудованою, її тіло повинно бути в одному блоці компіляції (наприклад, у заголовку). Це рідше в програмах на С.
Майкл Коне

1
визначення шаблону функції, що не є членом (він же нестатичний шаблон функції) не потребує вбудованого встроювання. Дивіться одне правило визначення (3.2 / 5).
deft_code

2
-1: inlineвсе ще потрібен, наприклад, для визначення функції у файлі заголовка (і це потрібно для вбудовування такої функції в декілька одиниць компіляції).
Мелебій

1
@ Étienne, що залежить від впровадження. На стандартне правило існує одне правило визначення, яке означає, що якщо ви наївно включите визначення функції у кілька одиниць перекладу, ви отримаєте помилку. Але якщо ця функція має inlineспецифікатор, її екземпляри автоматично згортаються в один за допомогою лінкера, і ODR не використовується.
Руслан

12

Коли я не повинен писати ключове слово 'inline' для функції / методу в C ++?

Якщо функція оголошена в заголовку і визначається в .cppфайлі, ви повинні НЕ написати ключове слово.

Коли компілятор не знатиме, коли зробити функцію / метод 'inline'?

Такої ситуації немає. Компілятор не може зробити функцію вбудованою. Все, що він може зробити, це вбудувати деякі або всі виклики до функції. Це не може зробити, якщо у нього немає коду функції (у такому випадку лінкеру потрібно це зробити, якщо він може це зробити).

Чи має значення додаток багатопотокове, коли пишете "inline" для функції / методу?

Ні, це зовсім не має значення.


Є випадки, коли доцільно використовувати вбудований файл у .cpp-файлі. Наприклад, застосовуючи оптимізацію до коду, який повністю залежить від реалізації.
Робін Девіс

@RobinDavies оновлена ​​відповідь. Здається, ви неправильно зрозуміли, що я хотів написати.
Йоханнес Шауб - ліб

5
  • Коли компілятор не знатиме, коли зробити функцію / метод 'inline'?

Це залежить від використовуваного компілятора. Не слід сліпо довіряти, що нині компілятори знають краще, ніж люди, як вбудувати, і ви ніколи не повинні використовувати його з міркувань продуктивності, оскільки це директива щодо зв’язків, а не натяк на оптимізацію. Хоча я погоджуюся, що ідеологічно ці аргументи правильно зустрічаються з реальністю, це може бути різна річ.

Прочитавши кілька потоків навколо, я з цікавості спробував вплив вбудованого коду, над яким я працюю. Результати полягали в тому, що я отримав помірну швидкість для GCC і не мав прискорення для компілятора Intel.

(Більш докладно: математичне моделювання з кількома критичними функціями, визначеними поза класом, GCC 4.6.3 (g ++ -O3), ICC 13.1.0 (icpc -O3); додавання рядка до критичних точок спричинено + 6% прискорення з кодом GCC).

Отже, якщо ви кваліфікуєте GCC 4.6 як сучасний компілятор, результат полягає в тому, що вбудована директива все ще має значення, якщо ви пишете інтенсивні завдання процесора і знаєте, де саме є вузьке місце.


6
Я хотів би побачити більше доказів для підтвердження ваших претензій. Введіть код, який ви тестуєте, а також виведіть асемблер із вбудованим ключовим словом та без нього. Будь-яка кількість речей могла б дати вам переваги від продуктивності.
void.pointer

1
Нарешті хтось, хто не тільки повторює те, що говорять інші, але насправді перевіряє ці твердження. Gcc насправді все ще розглядає вбудоване ключове слово як натяк (я думаю, Кланг повністю його ігнорує).
MikeMB

@ void.pointer: Чому в це так важко повірити? Якщо оптимізатори вже були ідеальними, то нові версії не могли б покращити продуктивність програми. Але вони регулярно роблять.
MikeMB

3

Насправді, майже ніколи. Все, що ви робите, - це запропонувати компілятору зробити задану функцію вбудованою (наприклад, замінити всі виклики цієї функції / w її тіла). Звичайно, гарантій немає: компілятор може ігнорувати директиву.

Компілятор, як правило, робить хорошу роботу з виявлення + оптимізації таких речей.


7
Проблема полягає в тому, що в C ++ inlineє семантична різниця (наприклад, у тому, як трактуються численні визначення), що важливо в деяких випадках (наприклад, шаблони).
Павло Мінаєв

4
inline використовується для вирішення випадків, коли символ має кілька визначень. Однак шаблони вже обробляються мовою. Один виняток - спеціалізована функція шаблону, у якої більше немає параметри шаблону (шаблон <>). До них відносяться скоріше як до функцій, ніж до шаблонів, і тому їм потрібне ключове слово вбудований для того, щоб зв’язати.
deft_code

2

gcc за замовчуванням не вбудовує жодних функцій при компілюванні без включеної оптимізації. Я не знаю про візуальну студію - deft_code

Я перевірив це на Visual Studio 9 (15.00.30729.01), компілюючи з / FAcs і переглядаючи код складання: Компілятор виробляв виклики до функцій учасників без оптимізації, включеної в режимі налагодження . Навіть якщо функція позначена __forceinline , вбудований код виконання не виробляється.


1
Увімкнути / Стіна розповісти про те, які функції, де позначено вбудованим, але насправді не вбудовано
пауль

0

Ви хочете поставити його на самому початку, перед типом повернення. Але більшість компіляторів це ігнорують. Якщо це визначено, і він має менший блок коду, більшість компіляторів все-таки вважають його вбудованим.


0

Якщо ви не пишете бібліотеку або не маєте особливих причин, ви можете забути про них inlineі використовувати натомість оптимізацію зв’язкового часу . Це знімає вимогу, що визначення функції повинно бути в заголовку, щоб воно було розглянуте для вбудованих одиниць компіляції, саме це inlineдозволяє.

(Але див. Чи є причина, чому не використовувати оптимізацію часу зв'язку? )


0

Вбудоване ключове слово запитує компілятор замінити виклик функції тілом функції, він спочатку оцінює вираз, а потім передає. Це зменшує накладні виклики функції, оскільки немає необхідності зберігати зворотну адресу, а пам'ять стека не потрібна для функції аргументи.

Коли користуватися:

  • Для підвищення продуктивності
  • Щоб зменшити накладні витрати.
  • Оскільки це лише запит до компілятора, певні функції не будуть накреслені * великими функціями
    • функції, що мають занадто багато умовних аргументів
    • рекурсивний код і код з петлями і т.д.

Вам може бути корисно знати, що це насправді не так. Рівень оптимізації від -0 до - Ofast - це те, що визначає, чи функція вбудована чи ні. Вбудований в регулярну компіляцію (-O0) не буде вбудовувати функцію незалежно від того, використовуєте ви inlineчи ні в C і C ++. C Inline: stackoverflow.com/a/62287072/7194773 C ++ inline: stackoverflow.com/a/62230963/7194773
Lewis Kelsey

0

C ++ рядний повністю відрізняється від C інлайн .

#include <iostream>
extern inline int i[];
int i [5];
struct c {
  int function (){return 1;} //implicitly inline
  static inline int j = 3; //explicitly inline
};
int main() {
  c j;
  std::cout << i;
}

inlineсамостійно впливає на компілятор, асемблер та лінкер. Це директива для компілятора, яка говорить лише, що випускає символ для цієї функції / даних, якщо він використовується в блоці перекладу, а якщо він є, то як методи класу, скажіть асемблеру, щоб він зберігав їх у розділі .section .text.c::function(),"axG",@progbits,c::function(),comdatабо .section .bss.i,"awG",@nobits,i,comdatдля даних.

З цього випливає .section name, "flags"MG, @type, entsize, GroupName[, linkage]. Наприклад, назва розділу .text.c::function(). axGозначає, що розділ може бути виділений, виконується і в групі, тобто назва групи буде вказана (і немає прапора M, тому не буде вказано енцизіт); @progbitsозначає, що розділ містить дані та не є порожнім; c::function()- це назва групи та групаcomdatзв'язок означає, що в усіх об'єктних файлах усі розділи, що зустрічаються з цією назвою групи з тегом comdat, будуть видалені з остаточного виконуваного файлу, за винятком 1, тобто компілятор гарантує, що в блоці перекладу є лише одне визначення, а потім каже асемблеру поставити він знаходиться у своїй власній групі в об’єктному файлі (1 розділ в 1 групі), і тоді лінкер переконається, що якщо будь-які файли об’єктів мають групу з тим самим іменем, то включають лише одну в остаточний .exe. Різниця між inlineі не використанням inlineтепер видима для асемблера і, як результат, лінкера, тому що він не зберігається в регулярному .dataабо іншому .textасемблері завдяки їх директивам.

static inlineу класі означає, що це визначення типу, а не декларація (дозволяє статичному члену визначитись у класі) та зробити його вбудованим; тепер він поводиться як вище.

static inlineв області файлу впливає лише компілятор. Це означає для компілятора: випускати символ для цієї функції / даних, лише якщо він використовується в блоці перекладу, і робити це як звичайний статичний символ (зберігати в.text /.data без директиви .globl). Для асемблера тепер немає різниці між staticіstatic inline

extern inline- це декларація, що означає, що ви повинні визначити цей символ у блоці перекладу або викинути помилку компілятора; якщо це визначено, тоді ставитесь до цього як до звичайного, inlineа до асемблера та лінкера різниці між extern inlineі не буде inline, тому це лише захист компілятора.

extern inline int i[];
extern int i[]; //allowed repetition of declaration with incomplete type, inherits inline property
extern int i[5]; //declaration now has complete type
extern int i[5]; //allowed redeclaration if it is the same complete type or has not yet been completed
extern int i[6]; //error, redeclaration with different complete type
int i[5]; //definition, must have complete type and same complete type as the declaration if there is a declaration with a complete type

Все вищезазначене без рядка помилки згортається на inline int i[5]. Очевидно, що якщо ви це зробили, extern inline int i[] = {5};то externбуло б проігноровано через явне визначення через призначення.

inlineна просторі імен див. це та це


-1

Розробляючи та налагоджуючи код, залишайте inlineйого. Це ускладнює налагодження.

Основною причиною їх додавання є оптимізація створеного коду. Зазвичай це торгує збільшеним кодовим простором для швидкості, але іноді inlineекономить як кодовий простір, так і час виконання.

Розширення подібної думки щодо оптимізації продуктивності до завершення роботи алгоритму - передчасна оптимізація .


12
inlineфункції, як правило, не вказуються, якщо не збираються з оптимізаціями, тому вони жодним чином не впливають на налагодження. Пам'ятайте, що це натяк, а не попит.
Павло Мінаєв

3
gcc за замовчуванням не вбудовує жодних функцій при компілюванні без включеної оптимізації. Я не знаю про візуальну студію
deft_code

Я працював над величезним проектом g ++, який включав налагодження. Можливо, інші параметри це заважали, але inlineфункції були накреслені. Встановити в них змістовну точку перелому неможливо.
wallyk

2
включення налагодження не припиняє вбудовувати в gcc. Якщо будь-яка оптимізація, де включена (-O1 або вище), gcc намагатиметься вбудувати найбільш очевидні випадки. Традиційно GDB важко переживає точки прориву та конструктори, особливо конструктори, що вбудовуються. Але це було виправлено в останніх версіях (принаймні, 6,7, можливо, і раніше).
deft_code

2
Додавання inlineнічого не сприятиме покращенню коду на сучасному компіляторі, який може зрозуміти, чи потрібно вбудовувати чи ні самостійно.
Девід Торнлі

-1

Коли слід вказувати:

1.Коли хочеться уникнути накладних речей, коли така функція викликається, як передача параметрів, передача керування, повернення керування тощо.

2.Функція повинна бути невеликою, часто дзвонити, а вбудована вбудована лінія є дійсно вигідною, оскільки, як правило, 80-20, намагайтеся зробити ті функції вбудованими, що має великий вплив на продуктивність програми.

Як ми знаємо, що inline - це лише запит на компілятор, подібний до реєстрації, і це коштуватиме вам за розміром коду Object.


"inline - це лише запит на компілятор, подібний до реєстрації" Вони схожі, тому що запити не мають або не мають нічого спільного з оптимізацією. inlineвтратила статус натяку на оптимізацію, і більшість компіляторів використовують його лише для створення кількох визначень - як IMO. Більше того, оскільки C ++ 11 registerповністю застаріло завдяки своєму попередньому значенню: "Я знаю краще, ніж компілятор, як оптимізувати": це тепер просто зарезервоване слово без поточного значення.
підкреслюйте_d

@underscore_d: Gcc все ще inlineпевною мірою слухає .
MikeMB

-1

Вбудована функція C ++ - це потужна концепція, яка зазвичай використовується для класів. Якщо функція вбудована, компілятор розміщує копію коду цієї функції у кожній точці, де функція викликається під час компіляції.

Будь-яка зміна вбудованої функції може вимагати перекомпіляції всіх клієнтів функції, оскільки компілятору потрібно буде ще раз замінити весь код, інакше він продовжиться зі старим функціоналом.

Щоб вбудувати функцію, поставте ключове слово inline перед назвою функції та визначте функцію перед тим, як виконувати будь-які дзвінки до функції. Компілятор може ігнорувати вбудований класифікатор у випадку, якщо визначена функція більше, ніж рядок.

Визначення функції у визначенні класу - це вбудоване визначення функції навіть без використання вбудованого специфікатора.

Далі наводиться приклад, який використовує вбудовану функцію для повернення максимум двох чисел

#include <iostream>

using namespace std;

inline int Max(int x, int y) { return (x > y)? x : y; }

// Main function for the program
int main() {
   cout << "Max (100,1010): " << Max(100,1010) << endl;

   return 0;
}

Докладнішу інформацію дивіться тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.