Когда мне следует написать ключевое слово «inline» для функции / метода?


564

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

Увидев некоторые ответы, некоторые связанные вопросы:

  • Когда я должен не написать ключевое слово «встроенный» для функции / методы в C ++?

  • Когда компилятор не будет знать, когда сделать функцию / метод «встроенным»?

  • Имеет ли значение, если приложение является многопоточным, когда кто-то пишет «inline» для функции / метода?


40
Если вы определяете функцию в заголовке, вам нужно объявить ее встроенной. В противном случае вы получите ошибки компоновщика о нескольких определениях функции.
Мартин Йорк,

15
@ Мартин: Если это не в определении класса, чтобы быть разборчивым.
Дэвид Торнли

20
@ Дэвид: чтобы быть более требовательным, это только потому, что такие функции неявно помечены inline(9.3 / 2).
Гонки легкости на орбите


Также см. Встроенные функции в C ++ FAQ. У них очень хорошее лечение inline.
jww

Ответы:


884

О, чувак, один из моих любимых мозолей.

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

Сказано, что inlineподсказывает компилятору, что вы думаете, что функция должна быть встроенной. Возможно, это было правдой в 1998 году, но спустя десятилетие компилятор не нуждается в таких подсказках. Не говоря уже о том, что люди обычно ошибаются, когда речь заходит об оптимизации кода, поэтому большинство компиляторов игнорируют «подсказку».

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

  • extern- используйте это имя переменной / функции в этом модуле перевода, но не жалуйтесь, если оно не определено. Компоновщик разберется и убедится, что весь код, который пытался использовать какой-либо символ extern, имеет свой адрес.

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

Примечание. Как правило, декларировать шаблоны inlineбессмысленно, поскольку они уже имеют семантику связывания inline. Однако явная специализация и конкретизация шаблонов требуютinline , чтобы использовать.


Конкретные ответы на ваши вопросы:

  • Когда мне следует написать ключевое слово «inline» для функции / метода в C ++?

    Только когда вы хотите, чтобы функция была определена в заголовке. Точнее, только когда определение функции может отображаться в нескольких единицах перевода. Хорошей идеей будет определить небольшие (как в одном вкладыше) функции в заголовочном файле, поскольку это дает компилятору больше информации для работы при оптимизации кода. Это также увеличивает время компиляции.

  • Когда не следует писать ключевое слово «inline» для функции / метода в C ++?

    Не добавляйте inline только потому, что вы думаете, что ваш код будет работать быстрее, если его встроит компилятор.

  • Когда компилятор не будет знать, когда сделать функцию / метод «встроенным»?

    Как правило, компилятор сможет сделать это лучше, чем вы. Однако у компилятора нет возможности встроить код, если у него нет определения функции. В максимально оптимизированном коде обычно все privateметоды встроены независимо от того, просите вы об этом или нет.

    В качестве отступления для предотвращения встраивания в GCC, используйте __attribute__(( noinline ))и в Visual Studio, используйте __declspec(noinline).

  • Имеет ли значение, если приложение является многопоточным, когда кто-то пишет «inline» для функции / метода?

    Многопоточность никак не влияет на вставку.


172
+1 Лучшее описание inline, которое я видел в ... (навсегда). Теперь я оторву вас и буду использовать это во всех моих объяснениях встроенного ключевого слова.
Мартин Йорк,

6
@ Зигги, я пытался сказать, что встраивание компилятора и inlineключевое слово не связаны. У вас есть правильная идея, хотя. Как правило, угадывание того, что было бы улучшено путем встраивания, очень подвержено ошибкам. Исключением из этого правила является один вкладыш.
deft_code

4
Этот ответ меня немного смущает. Вы говорите все это о том, что компилятор может встроить / не встроить вещи лучше. Затем вы говорите, что вы должны поместить один заголовок / маленькие функции в заголовок, и что компилятор не может встроить код без определения функции. Разве это не немного противоречиво? Почему бы просто не поместить все в файл cpp и позволить компилятору решить?
user673679

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

8
Всякий раз, когда я читаю что-то на счет совокупного знания Интернета, я думаю о знаменитой цитате Джона Лоутона: ирония информационного века заключается в том, что он придает новую респектабельность неосведомленному мнению.
II

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;
    }

  • Дело А:

    Компилировать :

    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обеих строк.


  • Дело Б:

    Компилировать (обратите внимание в обратном порядке) :

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

    Выход :

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

    Обсуждение :

    1. Этот случай утверждает , что уже обсуждалось в случае А .

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


  • Дело С:

    Компилировать (уведомление -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функции неопределенным поведением.
Р Саху

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

Ссылка на C ++ прямо говорит: «Если встроенная функция или переменная (начиная с C ++ 17) с внешней связью определяется по-разному в разных единицах перевода, поведение не определено». Таким образом, материал, который вы написали, специфичен для GCC, поскольку является побочным эффектом оркестровки процессов компиляции и компоновки. Также обратите внимание, что это может варьироваться в зависимости от версии.
Петр Фидлер

27

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


21

1) В настоящее время почти никогда. Если это хорошая идея встроить функцию, компилятор сделает это без вашей помощи.

2) Всегда. Смотрите № 1.

(Отредактировано, чтобы отразить, что вы разбили свой вопрос на два вопроса ...)


Да. Встроенный является лишь подсказкой для компилятора, и он может игнорировать вас. В наши дни компилятор, вероятно, знает лучше, чем программист, какие функции лучше всего встроить.
Марк Байерс

1
Да, но это менее актуально - для функции, которая должна быть встроена, ее тело должно быть в том же модуле компиляции (например, в заголовке). Это менее распространено в программах на Си.
Майкл Кохне

1
определение шаблона функции, не являющегося членом (иначе шаблон нестатической функции), не требует встроенного. См. Одно определение правила (3.2 / 5).
deft_code

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

1
@ Этьен, это зависит от реализации. В соответствии со стандартом существует одно правило определения, которое означает, что если вы наивно включите определение функции в несколько единиц перевода, вы получите ошибку. Но если эта функция имеет inlineспецификатор, ее экземпляры автоматически объединяются компоновщиком в один, а ODR не используется.
Руслан

12

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

Если функция объявлена ​​в заголовке и определена в .cppфайле, вам не следует писать ключевое слово.

Когда компилятор не будет знать, когда сделать функцию / метод «встроенным»?

Там нет такой ситуации. Компилятор не может сделать функцию встроенной. Все, что он может сделать, это встроить некоторые или все вызовы функции. Это не может быть сделано, если у него нет кода функции (в этом случае компоновщик должен сделать это, если он может это сделать).

Имеет ли значение, если приложение является многопоточным, когда кто-то пишет «inline» для функции / метода?

Нет, это не имеет значения.


В некоторых случаях целесообразно использовать inline в файле .cpp. Например, применение оптимизаций к коду, который полностью зависит от реализации.
Робин Дэвис,

@RobinDavies обновленный ответ. Кажется, вы не поняли, что я хотел написать.
Йоханнес Шауб -

5
  • Когда компилятор не будет знать, когда сделать функцию / метод «встроенным»?

Это зависит от используемого компилятора. Не стоит слепо полагать, что в настоящее время компиляторы лучше, чем люди, знают, как встроить код, и вам никогда не следует использовать его по соображениям производительности, потому что это директива о связях, а не подсказка по оптимизации. Хотя я согласен с тем, что идеологически правильны эти аргументы, столкновение с реальностью может быть другим.

После прочтения нескольких потоков я из любопытства попробовал влияние встроенного кода на код, над которым я только что работал, и в результате я получил измеримое ускорение для GCC и отсутствие ускорения для компилятора Intel.

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

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


6
Я хотел бы видеть больше доказательств в поддержку ваших утверждений. Пожалуйста, предоставьте код, который вы тестируете, а также вывод на ассемблере со встроенным ключевым словом и без него. Любое количество вещей могло бы дать вам выигрыш в производительности.
void.pointer

1
Наконец, кто-то, кто не только повторяет то, что говорят другие, но на самом деле проверяет эти утверждения. Gcc действительно все еще рассматривает ключевое слово inline как подсказку (я думаю, что clang полностью его игнорирует).
MikeMB

@ void.pointer: Почему в это так трудно поверить? Если оптимизаторы уже были совершенны, то новые версии не могли улучшить производительность программы. Но они регулярно делают.
MikeMB

3

На самом деле, почти никогда. Все, что вы делаете, это предлагаете компилятору сделать данную функцию встроенной (например, замените все вызовы этой функции / ее тело). Конечно, нет никаких гарантий: компилятор может игнорировать директиву.

Компилятор, как правило, хорошо справляется с обнаружением и оптимизацией подобных вещей.


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

4
Встроенный используется для разрешения случаев, когда символ имеет несколько определений. Тем не менее, шаблоны уже обрабатываются языком. Единственным исключением является специализированная функция шаблона, которая больше не имеет никаких параметров шаблона (template <>). Они обрабатываются скорее как функции, чем шаблоны, и поэтому для связывания необходимо ключевое слово inline.
deft_code

2

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

Я проверил это для Visual Studio 9 (15.00.30729.01), скомпилировав с / FAcs и посмотрев код сборки: компилятор вызывал функции-члены без оптимизации, включенной в режиме отладки . Даже если функция помечена __forceinline , встроенный код времени выполнения не создается.


1
Включить / Wall сообщить, какие функции были помечены как встроенные, но не были встроены
paulm

0

Вы хотите поместить это в самом начале, перед возвращаемым типом. Но большинство компиляторов игнорируют это. Если он определен и имеет меньший блок кода, большинство компиляторов все равно считают его встроенным.


0

Если вы не пишете библиотеку или у вас нет особых причин, вы можете забыть об этом inlineи использовать вместо этого оптимизацию времени соединения . Это устраняет требование о том, что определение функции должно быть в заголовке, чтобы его можно было рассмотреть для встраивания между модулями компиляции, и это именно то, чтоinline позволяет.

(Но посмотрите, есть ли причина, почему бы не использовать оптимизацию по времени ссылки? )


0

В линиюКлючевое слово запрашивает компилятор заменить вызов функции на тело функции, сначала он вычисляет выражение, а затем передает его. Он уменьшает накладные расходы на вызов функции, так как нет необходимости сохранять адрес возврата и стек памяти не требуется для функции аргументы.

Когда использовать:

  • Улучшить производительность
  • Чтобы уменьшить накладные расходы на звонки.
  • Поскольку это просто запрос к компилятору, некоторые функции не будут встроены * большие функции
    • функции, имеющие слишком много условных аргументов
    • рекурсивный код и код с циклами и т. д.

Вам может быть полезно знать, что на самом деле это не так. Уровень оптимизации от -O0 до - Ofast определяет, является ли функция встроенной или нет. Встроенная регулярная компиляция (-O0) не будет встроенной функцией независимо от того, используете вы ее inlineили нет в C и C ++. C Инлайн: stackoverflow.com/a/62287072/7194773 C ++ рядный: stackoverflow.com/a/62230963/7194773
Льюис Kelsey

0

C ++ inline полностью отличается от C inline .

#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для данных. Шаблонные экземпляры также идут в своих собственных группах comdat.

Это следует .section name, "flags"MG, @type, entsize, GroupName[, linkage]. Например, имя раздела .text.c::function(). axGозначает, что раздел является размещаемым, исполняемым и в группе, т. е. будет указано имя группы (и флаг М отсутствует, поэтому entsize не будет указан); @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.Функция должна быть небольшой, часто вызываться, и создание inline действительно выгодно, так как в соответствии с правилом 80-20, постарайтесь сделать функцию inline встроенной, что существенно повлияет на производительность программы.

Как мы знаем, inline - это просто запрос к компилятору, похожий на регистр, и он будет стоить вам при размере кода объекта.


«inline - это просто запрос к компилятору, похожий на регистр». Они похожи, потому что ни запросы, ни какое-либо отношение к оптимизации не имеют. inlineутратил свой статус подсказки по оптимизации, и большинство компиляторов используют его только для учета нескольких определений, как это должно быть в IMO. Более того, начиная с C ++ 11, registerон полностью устарел за его прежнее значение «я знаю лучше, чем компилятор, как оптимизировать»: теперь это просто зарезервированное слово без текущего значения.
underscore_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.