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

 

Самая актуальная документация по Visual Studio 2017: Документация по Visual Studio 2017.

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

В показанном ниже объявлении класса конструктор Account является встроенной функцией. Функции-члены GetBalance, Deposit и Withdraw не определены как inline, но могут быть реализованы как встроенные функции.

// Inline_Member_Functions.cpp  
class Account  
{  
public:  
    Account(double initial_balance) { balance = initial_balance; }  
    double GetBalance();  
    double Deposit( double Amount );  
    double Withdraw( double Amount );  
private:  
    double balance;  
};  
  
inline double Account::GetBalance()  
{  
    return balance;  
}  
  
inline double Account::Deposit( double Amount )  
{  
    return ( balance += Amount );  
}  
  
inline double Account::Withdraw( double Amount )  
{  
    return ( balance -= Amount );  
}  
int main()  
{  
}  

System_CAPS_ICON_note.jpg Примечание

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

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

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

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

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

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

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

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

Ключевое слово inline доступно только в C++. Ключевые слова __inline и __forceinline доступны как в C, так и в C++. Для обеспечения совместимости с предыдущими версиями ключевые слова _inline и __inline являются синонимами.

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

  • Рекурсивные функции.

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

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

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

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

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

Пример 1

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

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

Пример 2

// inline_keyword2.cpp  
// compile with: /EHsc /c  
#include <iostream>  
using namespace std;  
  
class MyClass {  
public:  
   void print() { cout << i << ' '; }   // Implicitly inline  
private:  
   int i;  
};  

Блок, относящийся только к системам Microsoft

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

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

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

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

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

  • В функции используется встроенный код ассемблера (кроме случаев компиляции с параметром /Og, /Ox, /O1 или /O2).

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

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

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

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

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

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

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

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

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

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

// when_to_use_inline_functions.cpp  
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;  
}  
int main()  
{  
}  

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

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

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

  • настройку нового кадра стека;

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

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

  • Назад

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

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

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

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

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

// inline_functions_macro.c  
#include <stdio.h>  
#include <conio.h>  
  
#define toupper(a) ((a) >= 'a' && ((a) <= 'z') ? ((a)-('a'-'A')):(a))  
  
int main() {  
   char ch;  
   printf_s("Enter a character: ");  
   ch = toupper( getc(stdin) );  
   printf_s( "%c", ch );  
}  
//  Sample Input:  xyz  
// Sample Output:  Z  
  

Цель выражения toupper(getc(stdin)) — предоставить возможность чтения символа с консольного устройства (stdin) и возможность преобразования в прописные буквы при необходимости.

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

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

// inline_functions_inline.cpp  
#include <stdio.h>  
#include <conio.h>  
  
inline char toupper( char a ) {  
   return ((a >= 'a' && a <= 'z') ? a-('a'-'A') : a );  
}  
  
int main() {  
   printf_s("Enter a character: ");  
   char ch = toupper( getc(stdin) );  
   printf_s( "%c", ch );  
}  

Пример ввода: a
Пример результатов выполнения: A

noinline
auto_inline

Показ: