что такое inline функция

INLINE-Функции

Хотя это и не относится в полной мере именно к объектно-ориентированному программирова­нию, одной из очень важных особенностей языка С++, которую нельзя найти в С, служит исполь­зование inline-функций. inline-функция — это такая функция, чье тело подставляется в каждую точ­ку вызова, вместо того, чтобы генерировать код вызова. Это подобно использованию параметризованных макросов в С. Имеются два способа создания inline-функции. Первый заключа­ется в использовании модификатора inline. Например, для создания inline-функции f, возвращаю­щей значение типа int и не имеющей параметров, достаточно объявить ее следующим образом:

Общая форма объявления inline-функции следующая:

Модификатор inline предшествует всем частям объявления функции.

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

Однако в тех случаях, когда размер inline-функции достаточно большой, общий объем программы также возрастает. Поэтому в качестве inline-функции обычно используются очень маленькие функ­ции. Большие функции реализуются обычным способом. В качестве примера следующая программа использует ключевое слово inline для организации подстановки функций get_i() и put_i():

Источник

Урок №101. Встроенные функции

Обновл. 13 Сен 2021 |

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

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

Гораздо проще изменить или обновить код в функции (что делается один раз), нежели искать и изменять все части кода в функции main() «на месте». Дублирование кода — хороший рецепт для ошибок и ухудшения производительности.

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

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

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

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

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

Источник

Встраиваемые функции (C++)

Функция, определенная в теле объявления класса, является встроенной.

Пример

В объявлении класса функции были объявлены без inline ключевого слова. inline Ключевое слово может быть указано в объявлении класса; результат будет таким же.

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

Функция-член класса по умолчанию имеет внешнюю компоновку, если только определение этой функции не содержит inline спецификатор. В предыдущем примере показано, что вам не нужно явно объявлять эти функции с помощью inline описателя. Использование inline в определении функции приводит к тому, что оно будет встроенной функцией. Однако не допускается повторное объявление функции inline после вызова этой функции.

inline Описатели и __inline указывают компилятору вставить копию тела функции в каждое место вызова функции.

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

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

Параметры и ключевые слова подстановки компилятор обрабатывает как рекомендации. Нет никакой гарантии, что функции будут встроены. Нельзя заставить компилятор подставляемь определенную функцию даже с помощью __forceinline ключевого слова. При компиляции с /clr компилятор не будет встроен в функцию, если к функции применены атрибуты безопасности.

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

Функции, на которые создаются ссылки посредством указателя в любом месте блока трансляции.

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

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

/Ob Параметр оптимизации компилятора позволяет определить, выполняется ли фактическое расширение функции.

/LTCG выполняет Межмодульное встраивание независимо от того, запрошены ли они в исходном коде.

Пример 1

Функции-члены класса могут быть объявлены встроенными либо с помощью inline ключевого слова, либо путем помещения определения функции в определение класса.

Пример 2

Только для систем Майкрософт

Даже при использовании __forceinline компилятор не может встроен код во всех обстоятельствах. Компилятор не может выполнить встраивание функции, если:

Функция или ее вызывающий объект компилируются вместе с /Ob0 (параметром по умолчанию для отладочных сборок).

В функции и вызывающем объекте используются разные типы (в одном — обработка исключений C++, а в другом — структурированная).

Функция имеет переменное число аргументов.

Функция является рекурсивной и не имеет #pragma inline_recursion(on) набора. С помощью этой директивы выполняется подстановка рекурсивных функций с глубиной по умолчанию, 16 вызовам. Чтобы уменьшить глубину встраивания, используйте inline_depth директиву pragma.

Функция является виртуальной, и для нее используется виртуальный вызов. Прямые вызовы виртуальных функций могут подставляться.

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

Функция также помечается naked __declspec модификатором.

Функция компилируется с помощью/OD или/Ob0. В таких случаях встраивание не ожидается.

Функция определена извне, в включенной библиотеке или другой записи преобразования либо является виртуальным целевым объектом вызова или адресатом косвенного вызова. Компилятор не может определить невстроенный код, который не удается найти в текущей записи преобразования.

Рекурсивные функции могут быть заменены встроенным кодом на глубину, заданную inline_depth директивой pragma, до максимум 16 вызовов. Начиная с этой глубины рекурсивные функции обрабатываются как вызовы на экземпляр функции. Глубина, для которой рекурсивные функции проверяются встроенным эвристическим методом, не может превышать 16. inline_recursion Директива pragma управляет встроенным расширением функции, находящегося в данный момент в расширении. Дополнительные сведения см. в описании параметра компилятора для расширения встроенной функции (/OB).

Завершение блока, относящегося только к системам Майкрософт

Дополнительные сведения об использовании inline спецификатора см. в следующих статьях:

Когда использовать встраиваемые функции

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

Point Класс можно определить следующим образом:

Предполагается, что обработка координат является относительно распространенной операцией в клиенте такого класса, указывая две функции доступа ( x и y в предыдущем примере), как inline правило, позволяет экономить издержки:

вызовы функций (включая передачу параметров и размещение адреса объекта в стеке);

сохранение кадра стека вызывающего объекта;

Настройка нового кадра стека

передачу возвращаемого значения;

Восстановление старого кадра стека

Встроенные функции и макросы

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

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

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

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

В следующем примере показан макрос, преобразующий строчные буквы в прописные.

Из-за реализации макроса getc выполняется один раз для определения того, что символ больше или равен «a», и один раз, чтобы определить, меньше ли он или равен z. Если символ находится в этом диапазоне, getc выполняется еще раз для преобразования символа в прописную букву. Это означает, что программа ожидает два или три символа, когда, в идеале, будет ожидать только один.

Подставляемые функции позволяют устранить описанную выше проблему.

Источник

Inline variables

Введение

Описание и примеры

Эволюция термина inline

One definition rule и как его нарушить?

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

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

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

Глобальные константы

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

Немного про линковку

В единицу трансляции включены файл реализации (.c/.cpp) и все его заголовочные файлы (.h/.hpp).
Если внутри единицы трансляции у объекта или функции есть внутреннее связывание, то этот символ виден компоновщику только внутри этой единицы трансляции. Если же у объекта или функции есть внешнее связывание, то компоновщик сможет видеть его при обработке других единиц трансляции. Использование ключевого слова static в глобальном пространстве имен дает символу внутреннее связывание. Ключевое слово extern дает внешнее связывание.
Компилятор по умолчанию дает символам следующие связывания:

Non-const глобальные переменные — внешнее связывание;

Const глобальные переменные — внутреннее связывание;

Функции — внешнее связывание.

Глобальные константы как переменные с внутренним связыванием

Один из способов сделать это:

Создайте заголовочный файл для хранения этих констант.

Внутри этого заголовочного файла определите пространство имен.

Добавьте все ваши константы в пространство имен (убедитесь, что они constexpr).

#include вашего заголовочного файла везде, где это нужно.

И используйте ваши константы:

Глобальные константы с внешним связыванием

Вышеприведенный метод имеет несколько потенциальных недостатков.Это легко использовать, но каждый раз, когда мы включаем заголвочный файл с константами в файл с кодом, каждая из этих переменных копируется в файл с кодом. Поэтому, если constants.h включается в 20 различных файлов кода, каждая из этих переменных дублируется 20 раз. Header guard не предотвратит это, так как она предотвращает только включение заголовка более одного раза в один cpp-файл, а не в несколько разных файлов с кодом. Это создает две проблемы:

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

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

Можем использовать их:

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

Глобальные константы как inline переменные

Все определения встроенной переменной должны быть идентичны (в противном случае, это приведёт к неопределенному поведению).

Определение встроенной переменной должно присутствовать в любом файле, использующем переменную.

Мы можем включать constants.h в любое количество cpp-файлов, но эти переменные будут созданы только один раз и совместно использоваться во всех файлах с кодом.

Инициализация статических полей класса

Рассмотрим класс со статическим полем. В C++14 вам нужно сначала объявить его в классе:

А затем определить его в отдельном блоке компиляции:

В C++14 определение переменной в классе приведет к ошибке во время компиляции. Однако в C++17 можно так:

Определение вне класса также возможно:

Замечания

Ещё раз подчеркну: все определения встроенной функции или переменной во всей программе должны быть идентичны. Нарушение этого правила приведёт к неопределённому поведению.

Статическая переменная-член (но не переменная пространства имен), объявленная constexpr, неявно является встроенной переменной.

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

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

Функция, определенная полностью внутри определения класса/структуры/объединения, неявно является встроенной функцией.

Функция, объявленная constexpr, неявно является встроенной функцией.

Удаленная функция неявно является встроенной функцией: ее определение (=delete) может отображаться в нескольких единицах трансляции.

Ссылки

При подготовке статьи, кроме материалов, на которые я ссылался в тексте, использовались:

В преддверии старта курса, приглашаем всех желающих записаться на бесплатный демоурок по теме: «Полезные инструменты в разработке на С++».

Источник

Встроенные (inline) функции

Использование функций высшего порядка влечёт за собой снижение производительности: во-первых, функция является объектом, а во-вторых, происходит захват контекста замыканием, то есть функции становятся доступны переменные, объявленные вне её тела. А выделения памяти (как для объекта функции, так и для её класса) и виртуальные вызовы занимают системные ресурсы.

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

Вместо создания объекта функции для параметра и генерации вызова, компилятор мог бы выполнить что-то подобное этому коду:

Разве это не то, чего мы хотели изначально?

Чтобы заставить компилятор поступить именно так, нам необходимо отметить функцию lock модификатором inline :

Модификатор inline влияет и на функцию, и на лямбду, переданную ей: они обе будут встроены в место вызова.

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

noinline

В случае, если вы хотите, чтобы только некоторые лямбды, переданные inline-функции, были встроены, вам необходимо отметить модификатором noinline те функции-параметры, которые встроены не будут:

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

Нелокальные return

В Kotlin мы можем использовать обыкновенный, безусловный return только для выхода из именованной функции или анонимной функции. Это значит, что для выхода из лямбды нам нужно использовать label. Обычный return запрещён внутри лямбды, потому что она не может заставить внешнюю функцию завершиться.

Но если функция, в которую передана лямбда, встроена, то return также будет встроен, поэтому так делать можно:

Такие return (находящиеся внутри лямбд, но завершающие внешнюю функцию) называются нелокальными ( non-local ). Мы используем такие конструкции в циклах, которые являются inline-функциями:

Заметьте, что некоторые inline-функции могут вызывать переданные им лямбды не напрямую в теле функции, а из иного контекста, такого как локальный объект или вложенная функция. В таких случаях, нелокальное управление потоком выполнения также запрещено в лямбдах. Чтобы указать это, параметр лямбды необходимо отметить модификатором crossinline :

break и continue пока что недоступны во встроенных лямбдах, но мы планируем добавить их поддержку

Параметры вещественного типа

Иногда нам необходимо получить доступ к типу, переданному в качестве параметра:

В этом примере мы осуществляем проход по дереву и используем рефлексию, чтобы проверить узел на принадлежность к определённому типу. Это прекрасно работает, но вызов выглядит не очень симпатично:

Что мы на самом деле хотим, так это передать этой функции тип, то есть вызвать её вот так:

В таких случаях inline-функции могут принимать параметры вещественного типа (reified type parameters). Чтобы включить эту возможность, мы можем написать что-то вроде этого:

Хотя рефлексия может быть не нужна во многих случаях, мы всё ещё можем использовать её с параметром вещественного типа:

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

Для низкоуровневого описания см. спецификацию.

Встроенные (inline) свойства (с версии 1.1)

Модификатор inline можно применять к методам доступа свойств, у которых нет теневых полей (backing field). Вы можете аннотировать отдельные методы доступа:

Также можно аннотировать свойство. В этом случае оба его метода доступа будут отмечены как встроенные:

В месте вызова встроенные методы доступа встраиваются как обычные inline-функции.

Ограничения для встроенных функций в public API

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *