Когда я должен написать ключевое слово "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
Обсуждение:
Даже если у вас должны быть идентичные определения ваших встроенных функций, компилятор C++ не помечает его, если это не так (на самом деле, из-за отдельной компиляции он не может проверить это). Это ваша собственная обязанность обеспечить это!
Линкер не жалуется на одно правило определения, так как
fun()
объявлен какinline
, Однако, потому что inline111.cpp является первым модулем перевода (который на самом деле вызываетfun()
обработано компилятором, компилятор создает егоfun()
после первого вызова в inline111.cpp. Если компилятор решит не расширятьсяfun()
при вызове из любой точки вашей программы (например, из inline222.cpp) вызовfun()
всегда будет связан с его экземпляром, созданным из inline111.cpp (вызовfun()
Внутри inline222.cpp также может быть создан экземпляр в этом модуле перевода, но он останется несвязанным). Действительно, это видно из идентичного&fun = 0x4029a0
распечаток.Наконец, несмотря на
inline
предложение компилятору на самом деле расширить однострочникfun()
, он полностью игнорирует ваше предложение,fun() = 111
в обеих строках.
Дело Б:
Компилировать (обратите внимание в обратном порядке):
g++ -std=c++11 inline222.cpp inline111.cpp
Выход:
inline111: fun() = 222, &fun = 0x402980 inline222: fun() = 222, &fun = 0x402980
Обсуждение:
Этот случай подтверждает то, что обсуждалось в случае A.
Обратите внимание на важный момент: если вы закомментируете фактический вызов
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
Обсуждение:
- Как описано здесь,
-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()
это имя группы, и у группы есть comdat
linkage означает, что во всех объектных файлах все разделы, встречающиеся с этим именем группы, помеченные 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
будут проигнорированы из-за явного определения через присваивание.
Встроенная функция 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
позволяет.
(Но посмотрите, есть ли причина, почему бы не использовать оптимизацию по времени ссылки?)