11.6 – Встраиваемые (inline) функции

Добавлено 14 июня 2021 в 04:48

Использование функций дает множество преимуществ, в том числе:

  • Код внутри функции можно использовать повторно.
  • Намного легче изменить или обновить код в функции (что нужно сделать один раз), чем для каждого экземпляра на месте. Дублирование кода – это рецепт для неэффективности и ошибок.
  • Они упрощают чтение и понимание вашего кода, так как вам не нужно знать, как реализована функция, чтобы понять, что она делает (при условии ответственного именования функций или комментариев).
  • Функции обеспечивают проверку типов, чтобы гарантировать, что аргументы вызова функции соответствуют параметрам функции (макросы, подобные функциям, этого не делают, что может привести к ошибкам).
  • Функции упрощают отладку вашей программы.

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

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

C++ предлагает способ сочетания преимуществ функций со скоростью кода, написанного на месте: встраиваемые (inline) функции. Ключевое слово inline используется для запроса у компилятора обработки вашей функции как встраиваемой функции. Когда компилятор компилирует ваш код, все встраиваемые (inline) функции разворачиваются на месте, то есть вызов функции заменяется копией содержимого самой функции, что устраняет накладные расходы на вызов функции! Обратной стороной является то, что поскольку встраиваемая функция разворачивается на месте для каждого вызова, это может сделать ваш скомпилированный код немного больше, особенно если встраиваемая функция длинная и/или выполняется много вызовов встраиваемой функции.

Рассмотрим следующий фрагмент:

int min(int x, int y)
{
    return x > y ? y : x;
}
 
int main()
{
    std::cout << min(5, 6) << '\n';
    std::cout << min(3, 2) << '\n';
    return 0;
}

Эта программа дважды вызывает функцию min(), что дважды добавляет накладные расходы на вызов функции. Поскольку min() – такая короткая функция, она является идеальным кандидатом для встраивания:

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

Теперь, когда компилятор будет компилировать main(), он создаст машинный код, как если бы main() была написана следующим образом:

int main()
{
    std::cout << (5 > 6 ? 6 : 5) << '\n';
    std::cout << (3 > 2 ? 2 : 3) << '\n';
    return 0;
}

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

Из-за возможности раздувания кода встраивание лучше всего подходит для коротких функций (например, не более нескольких строк), которые обычно вызываются внутри циклов и не имеют разветвлений. Также обратите внимание, что ключевое слово inline является только рекомендацией – компилятор может проигнорировать ваш запрос на встраивание функции. Вероятно, так будет, если вы попытаетесь встроить длинную функцию!

Наконец, современные компиляторы сейчас очень хорошо умеют автоматически встраивать функции – в большинстве случаев лучше, чем люди. Даже если вы не отметите функцию как встраиваемую, компилятор будет встраивать функции, которые, по его мнению, приведут к увеличению производительности. Таким образом, в большинстве случаев нет особой необходимости использовать ключевое слово inline. Позвольте компилятору обрабатывать встраиваемые функции за вас.

Лучшая практика


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

Встраиваемые функции не подпадают под правило "одно определение в программе".

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

Однако на встраиваемые функции не распространяется правило, согласно которому у вас в программе может быть только одно определение, поскольку встраиваемые функции на самом деле не приводят к компиляции реальной функции – следовательно, не будет конфликта, когда компоновщик попытается слинковать несколько файлов вместе.

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

Даже со встраиваемыми функциями обычно не следует определять глобальные функции в файлах заголовков.

Теги

C++ / CppinlineLearnCppДля начинающихОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.