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

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

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

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

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

Пример

В следующем объявлении класса конструктор является встроенной функцией, Account так как она определена в тексте объявления класса. Функции-члены GetBalanceDepositи Withdraw указаны inline в их определениях. Ключевое слово inline является необязательным в объявлениях функций в объявлении класса.

// account.h
class Account
{
public:
    Account(double initial_balance)
    {
        balance = initial_balance;
    }

    double GetBalance() const;
    double Deposit(double amount);
    double Withdraw(double amount);

private:
    double balance;
};

inline double Account::GetBalance() const
{
    return balance;
}

inline double Account::Deposit(double amount)
{
    balance += amount;
    return balance;
}

inline double Account::Withdraw(double amount)
{
    balance -= amount;
    return balance;
}

Примечание.

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

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

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

inline, __inline и __forceinline

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

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

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

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

Для совместимости с предыдущими версиями _inline и _forceinline являются синонимами __inline и __forceinline, соответственно, если не указан параметр компилятора /Za (отключение расширений языка).

Ключевое слово inline сообщает компилятору, что встроенное расширение предпочтительнее. Однако компилятор может игнорировать его. Два случая, когда такое поведение может произойти:

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

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

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

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

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

Пример 1

// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
    return a < b ? b : a;
}

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

Пример 2

// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>

class MyClass
{
public:
    void print() { std::cout << i; }   // Implicitly inline

private:
    int i;
};

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

Ключевое слово __inline эквивалентен inline.

Даже если __forceinlineкомпилятор не может встраить функцию, если:

  • Функция или вызывающий объект компилируется с /Ob0 помощью (параметр по умолчанию для отладочных сборок).
  • В функции и вызывающем объекте используются разные типы (в одном — обработка исключений C++, а в другом — структурированная).
  • Функция имеет переменное число аргументов.
  • Функция использует встроенную сборку, если только не компилируется с /Ox, /O1или /O2.
  • Функция рекурсивна и не имеет #pragma inline_recursion(on) набора. С помощью этой директивы выполняется подстановка рекурсивных функций с глубиной по умолчанию, 16 вызовам. Чтобы уменьшить глубину встраивание, используйте inline_depth pragma.
  • Функция является виртуальной, и для нее используется виртуальный вызов. Прямые вызовы виртуальных функций могут подставляться.
  • Программа принимает адрес функции, и вызов совершается через указатель на функцию. Прямые вызовы функций, чей адрес был принят, могут подставляться.
  • Функция также помечена модификатором naked__declspec .

Если компилятор не может встроить функцию, объявленную с __forceinline, он создает предупреждение уровня 1, за исключением следующих случаев:

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

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

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

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

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

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

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

// when_to_use_inline_functions.cpp
// compile with: /c
class Point
{
public:
    // Define "accessor" functions
    // as reference types.
    unsigned& x();
    unsigned& y();

private:
    unsigned _x;
    unsigned _y;
};

inline unsigned& Point::x()
{
    return _x;
}

inline unsigned& Point::y()
{
    return _y;
}

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

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

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

Макрос имеет некоторые общие сведения с функцией inline . Но есть важные различия. Рассмотрим следующий пример:

#include <iostream>

#define mult1(a, b) a * b
#define mult2(a, b) (a) * (b)
#define mult3(a, b) ((a) * (b))

inline int multiply(int a, int b)
{
    return a * b;
}

int main()
{
    std::cout << (48 / mult1(2 + 2, 3 + 3)) << std::endl; // outputs 33
    std::cout << (48 / mult2(2 + 2, 3 + 3)) << std::endl; // outputs 72
    std::cout << (48 / mult3(2 + 2, 3 + 3)) << std::endl; // outputs 2
    std::cout << (48 / multiply(2 + 2, 3 + 3)) << std::endl; // outputs 2

    std::cout << mult3(2, 2.2) << std::endl; // no warning
    std::cout << multiply(2, 2.2); // Warning C4244	'argument': conversion from 'double' to 'int', possible loss of data
}
33
72
2
2
4.4
4

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

  • Макросы всегда развертываются встроенными. Однако встроенная функция встраивается только в том случае, если компилятор определяет, что это оптимальное действие.
  • Макрос может привести к непредвиденному поведению, что может привести к тонким ошибкам. Например, выражение mult1(2 + 2, 3 + 3) расширяется, в 2 + 2 * 3 + 3 котором оценивается значение 11, но ожидаемый результат равен 24. Казалось бы, допустимое исправление заключается в добавлении скобок вокруг обоих аргументов макроса функции, что приведет к #define mult2(a, b) (a) * (b)решению проблемы, но по-прежнему может вызвать удивительное поведение, когда часть большего выражения. Это было показано в предыдущем примере, и проблема может быть решена путем определения макроса как такового #define mult3(a, b) ((a) * (b)).
  • Встроенная функция подвергается семантической обработке компилятором, а препроцессор расширяет макросы без того же преимущества. Макросы не являются типобезопасными, в то время как функции являются.
  • Выражения, передаваемые во встраиваемые функции в качестве аргументов, вычисляются один раз. В некоторых случаях выражения, передаваемые в макросы в качестве аргументов, можно вычислить несколько раз. Например, рассмотрим следующее:
#include <iostream>

#define sqr(a) ((a) * (a))

int increment(int& number)
{
    return number++;
}

inline int square(int a)
{
    return a * a;
}

int main()
{
    int c = 5;
    std::cout << sqr(increment(c)) << std::endl; // outputs 30
    std::cout << c << std::endl; // outputs 7

    c = 5;
    std::cout << square(increment(c)) << std::endl; // outputs 25
    std::cout << c; // outputs 6
}
30
7
25
6

В этом примере функция increment вызывается в два раза по мере расширения ((increment(c)) * (increment(c)))выраженияsqr(increment(c)). Это привело ко второму вызову increment возвращать 6, поэтому выражение оценивается в 30. Любое выражение, содержащее побочные эффекты, может повлиять на результат при использовании в макросе, проверьте полностью развернутый макрос, чтобы проверка, если это поведение предназначено. Вместо этого, если была использована встроенная функция square , функция increment будет вызываться только один раз, и будет получен правильный результат 25.

См. также

noinline
auto_inline