Когда я должен написать ключевое слово "inline" для функции / метода?

Когда я должен написать ключевое слово inline для функции / метода в C++?

Увидев некоторые ответы, некоторые связанные вопросы:

  • Когда не следует писать ключевое слово "inline" для функции / метода в C++?

  • Когда компилятор не будет знать, когда сделать функцию / метод "встроенным"?

  • Имеет ли значение, если приложение является многопоточным, когда пишется "inline" для функции / метода?

17 ответов

Решение

О, чувак, один из моих любимых мозолей.

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

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

  • static - имя переменной / функции нельзя использовать в других единицах перевода. Линкер должен убедиться, что он случайно не использует статически определенную переменную / функцию из другого модуля перевода.

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

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

Примечание: как правило, объявление шаблонов inline бессмысленно, так как они имеют семантику связи inline уже. Тем не мение, explicit специализация и создание шаблонов требуют inline использоваться.


Конкретные ответы на ваши вопросы:

  • Когда мне следует написать ключевое слово "inline" для функции / метода в C++?

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

  • Когда не следует писать ключевое слово "inline" для функции / метода в C++?

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

  • Когда компилятор не будет знать, когда сделать функцию / метод "встроенным"?

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

    Для предотвращения встраивания в GCC, используйте __attribute__(( noinline )) и в Visual Studio используйте __declspec(noinline),

  • Имеет ли значение, если приложение является многопоточным, когда пишется "inline" для функции / метода?

    Многопоточность никак не влияет на вставку.

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

Даны два исходных файла, такие как:

  • inline111.cpp:

    #include <iostream>
    
    void bar();
    
    inline int fun() {
      return 111;
    }
    
    int main() {
      std::cout << "inline111: fun() = " << fun() << ", &fun = " << (void*) &fun;
      bar();
    }
    
  • inline222.cpp:

    #include <iostream>
    
    inline int fun() {
      return 222;
    }
    
    void bar() {
      std::cout << "inline222: fun() = " << fun() << ", &fun = " << (void*) &fun;
    }
    

  • Дело А:

    Компилировать:

    g++ -std=c++11 inline111.cpp inline222.cpp
    

    Выход:

    inline111: fun() = 111, &fun = 0x4029a0
    inline222: fun() = 111, &fun = 0x4029a0
    

    Обсуждение:

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

    2. Линкер не жалуется на одно правило определения, так как fun() объявлен как inline, Однако, потому что inline111.cpp является первым модулем перевода (который на самом деле вызывает fun() обработано компилятором, компилятор создает его fun() после первого вызова в inline111.cpp. Если компилятор решит не расширяться fun() при вызове из любой точки вашей программы (например, из inline222.cpp) вызов fun() всегда будет связан с его экземпляром, созданным из inline111.cpp (вызов fun() Внутри inline222.cpp также может быть создан экземпляр в этом модуле перевода, но он останется несвязанным). Действительно, это видно из идентичного &fun = 0x4029a0 распечаток.

    3. Наконец, несмотря на inline предложение компилятору на самом деле расширить однострочник fun(), он полностью игнорирует ваше предложение, fun() = 111 в обеих строках.


  • Дело Б:

    Компилировать (обратите внимание в обратном порядке):

    g++ -std=c++11 inline222.cpp inline111.cpp
    

    Выход:

    inline111: fun() = 222, &fun = 0x402980
    inline222: fun() = 222, &fun = 0x402980
    

    Обсуждение:

    1. Этот случай подтверждает то, что обсуждалось в случае A.

    2. Обратите внимание на важный момент: если вы закомментируете фактический вызов fun() в inline222.cpp (например, комментарий cout -statement в inline222.cpp полностью) тогда, несмотря на порядок компиляции ваших единиц перевода, fun() будет создан при первой встрече с вызовом в inline111.cpp, что приведет к распечатке для случая B как inline111: fun() = 111, &fun = 0x402980,


  • Дело С:

    Компилировать (уведомление -O2):

    g++ -std=c++11 -O2 inline222.cpp inline111.cpp
    

    или же

    g++ -std=c++11 -O2 inline111.cpp inline222.cpp
    

    Выход:

    inline111: fun() = 111, &fun = 0x402900
    inline222: fun() = 222, &fun = 0x402900
    

    Обсуждение:

    1. Как описано здесь, -O2 Оптимизация побуждает компилятор расширять функции, которые могут быть встроены (обратите внимание также, что -fno-inline по умолчанию без параметров оптимизации). Как видно из рисунка здесь, fun() было фактически расширено (в соответствии с его определением в этой конкретной единице перевода), что привело к двум fun() распечаток. Несмотря на это, существует только один глобально связанный случай fun() (как того требует стандарт), как видно из идентичного &fun распечатывать.

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

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

2) Всегда. Смотрите № 1.

(Отредактировано, чтобы отразить, что вы разбили свой вопрос на два вопроса...)

Когда не следует писать ключевое слово "inline" для функции / метода в C++?

Если функция определена в .cpp файл, вы не должны писать ключевое слово.

Когда компилятор не будет знать, когда сделать функцию / метод "встроенным"?

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

Имеет ли значение, если приложение является многопоточным, когда пишется "inline" для функции / метода?

Нет, это не имеет значения.

  • Когда компилятор не будет знать, когда сделать функцию / метод "встроенным"?

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

После прочтения нескольких потоков я из любопытства попробовал влияние встроенного кода на код, над которым я только что работал, и в результате я получил измеримое ускорение для GCC и отсутствие ускорения для компилятора Intel.

(Более подробно: математическое моделирование с несколькими критическими функциями, определенными вне класса, GCC 4.6.3 (g++ -O3), ICC 13.1.0 (icpc -O3); добавление встроенных в критические точки вызвало +6% ускорение с помощью кода GCC).

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

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

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

gcc по умолчанию не включает никаких функций при компиляции без включенной оптимизации. Я не знаю о визуальной студии - deft_code

Я проверил это для Visual Studio 9 (15.00.30729.01), скомпилировав с /FAcs и посмотрев код сборки: компилятор вызывал функции-члены без оптимизации, включенной в режиме отладки. Даже если функция помечена __forceinline, встроенный код времени выполнения не создается.

F.5: Если функция очень мала и критична по времени, объявите ее встроенной

Причина : некоторые оптимизаторы умеют встраивать без подсказок программиста, но не полагаются на это. Мера! За последние 40 лет или около того нам обещали компиляторы, которые могут встраивать лучше, чем люди, без подсказок со стороны людей. Мы все еще ждем. Указание inline (явно или неявно при написании функций-членов внутри определения класса) побуждает компилятор выполнять свою работу лучше.

Источник: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#Rf-inline

Примеры и исключения см. В источнике (см. Выше).

Один вариант использования может возникнуть при наследовании. Например, если все нижеприведенные случаи верны:

  • у вас есть базовый класс некоторого класса
  • базовый класс должен быть абстрактным
  • базовый класс не имеет чисто виртуального метода, кроме деструктора
  • вы не хотите создавать файл cpp для базового класса, потому что зря

затем вы должны определить деструктор; в противном случае у вас будет несколько undefined referanceошибки компоновщика. Более того, вам нужно не только определить, но и определить деструктор с ключевым словом inline; в противном случае у вас будет multiple definitionошибки компоновщика.

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

Приведем пример:

База.ч:

      class Base {
public:
    Base(SomeElementType someElement) noexcept : _someElement(std::move(someElement)) {}

    virtual ~Base() = 0;

protected:
    SomeElementType _someElement;
}

inline Base::~Base() = default;

Производный1.ч:

      #include "Base.h"

class Derived1 : public Base {
public:
    Derived1(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}

    void DoSomething1() const;
}

Производный1.cpp:

      #include "Derived1.h"

void Derived1::DoSomething1() const {
    // use _someElement 
}

Производный2.ч:

      #include "Base.h"

class Derived2 : public Base {
public:
    Derived2(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}

    void DoSomething2() const;
}

Производный2.cpp:

      #include "Derived2.h"

void Derived2::DoSomething2() const {
    // use _someElement 
}

Как правило, абстрактные классы имеют несколько чисто виртуальных методов, отличных от конструктора или деструктора. Таким образом, вам не нужно было разделять деклерацию и определение виртуального деструктора базового класса, вы могли просто написать virtual ~Base() = default;о декларации классов. Однако в нашем случае это не так.

Насколько я знаю, MSVC позволяет вам написать что-то подобное при деклерации класса: virtual ~Base() = 0 {}. Таким образом, вам не нужно разделять деклерацию и определение с помощью встроенного ключевого слова. Но это будет работать только с компилятором MSVC.

Пример реального мира:

Базисцептион.h:

      #pragma once

#include <string>

class BaseException : public std::exception {
public:
    BaseException(std::string message) noexcept : message(std::move(message)) {}
    virtual char const* what() const noexcept { return message.c_str(); }

    virtual ~BaseException() = 0;

private:
    std::string message;
};

inline BaseException::~BaseException() = default;

SomeException.h:

      #pragma once

#include "BaseException.h"

class SomeException : public BaseException {
public:
    SomeException(std::string message) noexcept : BaseException(std::move(message)) {}
};

SomeOtherException.h:

      #pragma once

#include "BaseException.h"

class SomeOtherException : public BaseException {
public:
    SomeOtherException(std::string message) noexcept : BaseException(std::move(message)) {}
};

основной.cpp:

      #include <SomeException.h>
#include <SomeOtherException.h>

#include <iostream>

using namespace std;

static int DoSomething(int argc) {
    try {
        switch (argc) {
        case 0:
            throw SomeException("some");
        case 1:
            throw SomeOtherException("some other");
        default:
            return 0;
        }
    }
    catch (const exception& ex) {
        cout << ex.what() << endl;
        return 1;
    }
}

int main(int argc, char**) {
    return DoSomething(argc);
}

C++ рядный полностью отличается от C инлайн.

#include <iostream>
extern inline int i[];
int i [5];
struct c {
  int function (){return 1;} //implicitly inline
  static inline int j = 3; //explicitly inline
};
int main() {
  c j;
  std::cout << i;
}

inlineсам по себе влияет на компилятор, ассемблер и компоновщик. Это директива для компилятора, говорящая о том, что для этой функции / данных используется только символ, если он используется в блоке перевода, и если это так, то, как и методы класса, скажите ассемблеру, чтобы он сохранил их в разделе.section .text.c::function(),"axG",@progbits,c::function(),comdat или .section .bss.i,"awG",@nobits,i,comdatдля данных. Экземпляры шаблонов также входят в свои собственные группы comdat.

Это следует .section name, "flags"MG, @type, entsize, GroupName[, linkage]. Например, название раздела.text.c::function(). axG означает, что секция может быть размещена, исполняема и находится в группе, т.е. имя группы будет указано (и нет флага M, поэтому размер не будет указан); @progbits означает, что раздел содержит данные и не пустой; c::function() это имя группы, и у группы есть comdatlinkage означает, что во всех объектных файлах все разделы, встречающиеся с этим именем группы, помеченные comdat, будут удалены из конечного исполняемого файла, за исключением 1, то есть компилятор проверяет, что есть только одно определение в единице перевода, а затем сообщает ассемблеру, чтобы он поместил он находится в своей собственной группе в объектном файле (1 раздел в 1 группе), а затем компоновщик будет следить за тем, чтобы, если какие-либо объектные файлы имеют группу с тем же именем, то включит только одну в окончательный.exe. Разница междуinline и не используя inline теперь виден ассемблеру и, как следствие, компоновщику, потому что он не хранится в обычном .data или .text ассемблером в соответствии с их директивами.

static inlineв классе означает, что это определение типа, а не объявление (позволяет определить статический член в классе) и сделать его встроенным; теперь он ведет себя как указано выше.

static inlineв области файла влияет только на компилятор. Для компилятора это означает: выдавать символ для этой функции / данных только в том случае, если он используется в блоке перевода, и делать это как обычный статический символ (хранить в тексте / данных без директивы.globl). Для ассемблера теперь нет разницы междуstatic а также static inline

extern inlineэто объявление, которое означает, что вы должны определить этот символ в единице перевода или выдать ошибку компилятора; если он определен, то относитесь к нему как к обычномуinline а для ассемблера и компоновщика не будет разницы между extern inline а также inline, так что это только защита компилятора.

extern inline int i[];
extern int i[]; //allowed repetition of declaration with incomplete type, inherits inline property
extern int i[5]; //declaration now has complete type
extern int i[5]; //allowed redeclaration if it is the same complete type or has not yet been completed
extern int i[6]; //error, redeclaration with different complete type
int i[5]; //definition, must have complete type and same complete type as the declaration if there is a declaration with a complete type

Все вышеперечисленное без строки ошибки сворачивается в inline int i[5]. Очевидно, если бы вы сделалиextern inline int i[] = {5}; тогда extern будут проигнорированы из-за явного определения через присваивание.

inlineв пространстве имен, посмотрите это и это

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

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

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

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

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

#include <iostream>

using namespace std;

inline int Max(int x, int y) { return (x > y)? x : y; }

// Main function for the program
int main() {
   cout << "Max (100,1010): " << Max(100,1010) << endl;

   return 0;
}

Для получения дополнительной информации см. здесь.

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

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

Когда использовать:

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

При разработке и отладке кода оставьте inline из. Это усложняет отладку.

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

Подобная мысль об оптимизации производительности до завершения алгоритма является преждевременной оптимизацией.

Когда следует включить:

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

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

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

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

(Но посмотрите, есть ли причина, почему бы не использовать оптимизацию по времени ссылки?)

Другие вопросы по тегам