Всегда ли код inline-функции подставляется в место вызова?

Ответ

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

Основные причины, по которым компилятор может проигнорировать inline:

  1. Сложность функции: Функция слишком большая или содержит циклы, рекурсию, switch.
  2. Виртуальные функции: Виртуальные вызовы обычно не инлайнятся из-за динамической диспетчеризации.
  3. Отключенная оптимизация: Например, при сборке в режиме отладки (-O0).
  4. Взятие адреса функции: Если адрес функции где-то используется, компилятору необходимо создать ее тело.

Пример:

// Объявление с inline — решение об инлайнинге за компилятором
inline int calculateSquare(int x) {
    return x * x;
}

// Компилятор, скорее всего, заинлайнит этот простой вызов:
// int result = 5 * 5;
int result = calculateSquare(5);

// Но может не инлайнить здесь из-за косвенного вызова через указатель
int (*funcPtr)(int) = &calculateSquare;
int result2 = funcPtr(10);

Современная практика: Ключевое слово inline чаще используется для разрешения нарушений ODR (One Definition Rule). Если функция определена (а не только объявлена) в заголовочном файле, ее следует помечать как inline, чтобы при включении заголовка в несколько единиц трансляции не возникало ошибок линковки из-за множественных определений. Фактическая подстановка кода — это задача оптимизатора.

Ответ 18+ 🔞

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

Смотри, есть у нас ключевое слово inline. Многие думают, что это железная команда компилятору: «Мужик, вот эту функцию — встраивай, и точка!». А на деле это просто вежливая просьба, типа «можно я тут пройду?». Компилятор посмотрит на неё, на тебя, и скажет: «Ну, знаешь ли, я тут подумал... да похуй, не буду».

Почему он может послать тебя нахуй с твоим inline?

  1. Функция — говноглыста. Слишком большая, с циклами, рекурсией, switch на десять экранов. Компилятор не дурак — он понимает, что если это везде воткнуть, бинарник раздует так, что соседний SSD обзавидуется. Ему же потом с этим работать.
  2. Функция виртуальная. Тут, ёпта, вообще отдельная история. Как компилятору заранее угадать, какую реализацию в рантайме вызывать? Он не экстрасенс, в конце концов. Поэтому виртуальные вызовы почти никогда не инлайнятся.
  3. Ты собрал проект в режиме дебага (-O0). Ну тут вообще пиздец. Ты сам ему сказал: «Не оптимизируй ничего, мне отлаживать надо!». А потом удивляешься, почему он тебя не слушает. Сам дурак.
  4. Ты взял адрес этой функции. Ну вот представим: int (*funcPtr)(int) = &calculateSquare;. Как компилятору её инлайнить, если тебе нужен её адрес в памяти? Придёшь ты по адресу, а там — хуй с горы, пусто. Неловко получится. Поэтому он обязан создать нормальное тело функции.

Пример, чтобы совсем понятно стало:

// Ты написал — типа, инлайнь это, пожалуйста.
inline int calculateSquare(int x) {
    return x * x;
}

// Тут компилятор посмотрит: «О, простое умножение. Да хуй с ним, заинлайню».
// Получится просто int result = 5 * 5;
int result = calculateSquare(5);

// А тут он уже задумается. Адрес-то берёшь!
int (*funcPtr)(int) = &calculateSquare;
int result2 = funcPtr(10); // Тут уже вряд ли будет инлайн.

Так зачем оно вообще нужно, спросишь ты?

А вот сейчас главное, блядь, слушай. В современном C++ inline — это в первую очередь не про оптимизацию, а про законность. Про то, чтобы линкер тебя не сожрал.

Если ты пишешь функцию прямо в .h файле (определяешь, а не просто объявляешь), и этот хедер тянут несколько .cpp файлов, то в каждом из них будет своя копия этой функции. Линкер соберёт всё вместе и охуеет: «Мужик, а у тебя тут одна и та же функция в трёх экземплярах! Какой из них правильный? Я запутался, всё, пиздец!» — и вывалит тебе ошибку.

Вот чтобы этого не было, ты и пишешь inline. Этим ты говоришь линкеру: «Чувак, расслабься, эти все одинаковые функции — они как бы одна. Выбери любую, да и хуй с ним». И линкер успокаивается.

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