Что такое инлайн
Перейти к содержимому

Что такое инлайн

INLINE-Функции

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

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

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

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

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

#include
class cl int i;
public:
int get_i();
void put_i(int j);
>;
inline int cl::get_i()
return i;
>
inline void cl::put_i(int j)
i = j;
>
int main()
cl s;
s.put i(10);
cout return 0;
>

Если откомпилировать эту версию программы и сравнить ее с программой, в которой ключе­вое слово inline не используется, то можно обнаружить, что версия с подстановкой на несколько байт меньше. Вызовы функций get_i() и put_i() также выполняются быстрее. Следует запомнить, однако, что если бы функции get_i() и put_i() были очень большими, то inline-версия программы была бы больше, чем версия без inline-функций. Но в любом случае она исполняется быстрее.

Важно ясно понимать, что технически inline представляет запрос к компилятору сгенерировать подставляемый код, inline не является командой. В некоторых ситуациях компилятор не будет выполнять этот запрос. Например, некоторые компиляторы не подставляют функции, содержа­щие циклы, switch и операторы goto. Borland С не подставляет функции, использующие исключе­ния или имеющие в качестве параметра объект, содержащий деструктор. Он также не подставляет функций, возвращающих объекты, имеющие деструкторы.

Инлайн-команды в Telegram и как поделиться товаром Telegramius

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

305 показов
2.1K открытий
Что такое инлайн-команды в телеграм

Инлайн-команды (инлайн-режим бота) позволяет всем пользователям телеграм взаимодействовать с вашим ботом, не нажимая на кнопку «Старт» («Начать» в iOS). Это такие специальные запросы, начинающиеся с символа «@». Например для поиска анимаций можно использовать @gif alf и мессенджер покажет вам анимации пришельца с планеты Мелмок и другие релевантные гифки.

Теперь ваши боты, подключенные к сервису Telegramius, облалают возможностью вызова товаров, но сначала нужно включить инлайн-режим вашего бота.

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

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

Я думаю, не для кого не секрет, что древние компьютеры требовали при написании ПО для тяжелых вычислений достаточно таки серьезного подхода с позиции программиста. Выкручиваться и экономить приходилось на всём, иначе время работы программы увеличивалось в разы. Это сейчас мы гоняем гигабайтные игрушки не особо жалуясь на скорость работы. В то время это было чрезвычайно критично, и одним из способов сократить время работы, как раз являлись inline (встроенные) функции. Сейчас я попробую более менее доступно рассказать почему.

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

int factorial ( )
int result = 1 ;
for ( i = 2 ; i < 5 ; i ++ ) return result ;

Достаточно простые вычисления факториала (5!) в цикле for, и возврат результата из функции. С++ расценивает эту функцию как некий блок операций, сгруппированный в отдельный блок. Блок этот после компиляции помещается в ячейки памяти единожды в коде, и тело функции (цикл в данном случае) нигде больше в скомпилированной программе не повторяется. Все красиво – получается некий участок памяти, принадлежавший программе, на который процессор при необходимости перескакивает с того места, где находит вызов.

cout << factorial ( ) ;

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

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

Если описать наш факториал так:

inline int factorial ( )
int result = 1 ;
for ( i = 2 ; i < 5 ; i ++ ) return result ; cout << factorial ( ) ;

мы получим разворот функции в набор операторов:

for ( i = 2 ; i < 5 ; i ++ ) cout << result ;

как будто бы сами в коде написали в этом месте.

Соответственно код типа:

cout << factorial ( ) << factorial ( ) / 5 ; for ( i = 2 ; i < 5 ; i ++ ) cout << result ; for ( i = 2 ; i < 5 ; i ++ ) cout << result / 5 ;

Если мерить философски – количество кода с inline функцией увеличилось. Вместо одной строки вызова функции ее тело, подставленное вместо вызова , дало целых 6 строк. Так же будет и в скомпилированной программе – количество операторов возрастет многократно – на столько, сколько операторов в теле функции и сколько раз ее вписали в программу.

Т.е. различие между inline функцией и обычной функцией – дублирование кода тела функции везде, где она оказывается задействована. В обычной функции, её тело находится в единственном экземпляре в одном и том же месте внутри программы.

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

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

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

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

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

lock(l)

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

l.lock() try < foo() >finally

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

inline fun lock(lock: Lock, body: () -> T): T < /*. */ >

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

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

noinline

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

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) < /*. */ >

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

If an inline function has no inlinable function parameters and no > [reified type parameters](#reified-type-parameters), the compiler will issue a warning, since inlining such functions > is very unlikely to be beneficial (you can use the `@Suppress(«NOTHING_TO_INLINE»)` annotation to suppress the warning > if you are sure the inlining is needed). —>

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

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

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

fun foo() < ordinaryFunction < return // ERROR: нельзя заставить `foo` завершиться здесь >> 

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

fun foo() < inlined < return // OK: лямбда встроена >> 

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

fun hasZeros(ints: List): Boolean < ints.forEach < if (it == 0) return true // return из hasZeros >return false > 

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

inline fun f(crossinline body: () -> Unit) < val f = object: Runnable < override fun run() = body() >// . > 

`break` and `continue` are not yet available in inlined lambdas, but we are planning to support them, too. —>

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

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

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

fun TreeNode.findParentOfType(clazz: Class): T? < var p = parent while (p != null && !clazz.isInstance(p)) < p = p.parent >@Suppress("UNCHECKED_CAST") return p as T? > 

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

treeNode.findParentOfType(MyTreeNode::class.java) 

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

treeNode.findParentOfType() 

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

inline fun TreeNode.findParentOfType(): T? < var p = parent while (p != null && p !is T) < p = p.parent >return p as T? > 

В коде выше тип параметра определяется с помощью модификатора reified , но он доступен внутри функции почти так же, как и обычный класс. Так как функция встроена, то для работы таких операторов как !is и as рефлексия не нужна. Также, вы можете вызывать её таким же образом, как было упомянуто выше: myTree.findParentOfType() .

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

inline fun membersOf() = T::class.members fun main(s: Array) < println(membersOf().joinToString("\n")) > 

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

Встроенные свойства

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

val foo: Foo inline get() = Foo() var bar: Bar get() = . inline set(v)

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

inline var bar: Bar get() = . set(v)

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

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

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

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

Чтобы исключить риск двоичной несовместимости, вызванной изменением non-public API модуля, public API inline-функциям не разрешается использовать объявления non-public-API, т.е. private и internal .

Объявление с модификатором internal может быть аннотировано при помощи @PublishedApi , что позволит его использовать в public API inline-функциях. Когда встроенная функция с модификатором доступа internal помечена как @PublishedApi , её тело тоже проверяется, как если бы она была public.

© 2015—2023 Open Source Community

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

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