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

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


person shreyasva    schedule 23.03.2010    source источник
comment
Также см. stackoverflow.com/questions/908830 /   -  person Johannes Schaub - litb    schedule 23.03.2010


Ответы (7)


Да, функции, определенные внутри тела класса, неявно являются inline.

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

person CB Bailey    schedule 23.03.2010

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

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

class A {
public:
  void f() { ... your code ... }
};

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

A__f_v:

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

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

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

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

рассмотрите этот встроенный код:

inline int add(int a, int b) { return a + b; }

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

И, если вам случится передавать константы:

int c= add(5,4);

Это разрешается во время компиляции, и кода нет.

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

На другом конце спектра предположим, что вы запрашиваете встроенный фрагмент кода из 1000 строк. Даже если ваш компилятор достаточно глуп, чтобы согласиться с этим, единственное, что вы сохраните, — это сам вызов, и цена будет заключаться в том, что каждый раз, когда вы его вызываете, компилятор должен вставлять весь этот код. Если вы вызываете этот код n раз , ваш код увеличивается на размер подпрограммы * n. Таким образом, все, что больше 10 строк, практически не стоит встраивать, за исключением особого случая, когда он вызывается очень небольшое количество раз. Примером этого может быть частный метод, вызываемый только двумя другими.

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

person Dov    schedule 23.03.2010
comment
Ты просто взорвал мой разум. Если я определяю функцию обычным способом (с определением здесь и реализацией там) и помещаю ключевое слово inline впереди (реализации? определения? обоих?), то компилятор рассмотрит возможность ее встраивания? А это лучше? Моя главная проблема со встроенными функциями заключается в том, что они уродливы и делают строку длинной. Вы хотите сказать, что я могу иметь встроенные функции, которые выглядят просто сексуально и легко читаются... и не только это, но и то, что я должен помечать маленькие функции таким образом? Надеюсь, вы активный пользователь и ответите на этот комментарий. - person Ziggy; 12.08.2011
comment
Если вы определяете функцию с помощью прототипа с реализацией в том же заголовочном файле, помеченной встроенной, она будет встроенной. Если ваша реализация находится в другом файле, компилятор не может сделать ее встроенной. Это не телепат, он не знает, какой код генерировать. Я лично не возражаю против небольшого дополнительного кода в заголовке, вместо того, чтобы писать его дважды, но вы, безусловно, можете сделать это, если хотите. - person Dov; 14.08.2011
comment
@ziggy, если я правильно понимаю ваш вопрос, да, если ваша функция короткая, то определение ее встроенной в файле заголовка может дать огромный выигрыш в скорости выполнения, при этом немного замедляя время компиляции, а также требуя любых символов, на которые есть ссылки во встроенной строке. функция быть определена. Это означает, что если ваш встроенный код ссылается на другие объекты, ваш заголовок может включать другие заголовки. Таким образом, существует потенциально большая стоимость времени перекомпиляции. Но для среднего метода (геттер/сеттер, вычисление чего-то с приватными переменными) встраивание — это огромная победа. - person Dov; 14.08.2011

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

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

person wilhelmtell    schedule 23.03.2010
comment
Зависит от компилятора. В общем, встроенный - это подсказка, которая мало что делает. Я думаю, что большинство компиляторов, предназначенных для встроенных платформ, более внимательно прислушиваются к советам, и это действительно единственная область, где вы можете превзойти компилятор после тщательного проектирования. - person marr75; 23.03.2010

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

person Liz Albin    schedule 23.03.2010

Это запрос к компилятору, который он может игнорировать.

person Vijay Mathew    schedule 23.03.2010
comment
Нет, это требование. Если функция определена в теле класса, ее следует рассматривать так, как если бы она была объявлена ​​inline. - person CB Bailey; 23.03.2010
comment
как и любой запрос inline, который компилятор может игнорировать. - person shoosh; 23.03.2010
comment
Поскольку его можно проигнорировать, и большинство компиляторов будут встраиваться там, где это уместно, даже если вы этого не запрашиваете, это довольно бесполезный совет. - person marr75; 23.03.2010
comment
@shoosh: будь то неявный или явный, компилятор не может игнорировать правила для встроенных функций. Например, тот факт, что они могут правомерно определяться в нескольких единицах перевода. - person CB Bailey; 23.03.2010
comment
@ marr75: это не бесполезно; это позволяет вам определять функцию более чем в одной единице компиляции, что в значительной степени необходимо для ее встраивания. Он просто не делает то, что вы, кажется, хотите (т.е. принудительно встраивает функцию). - person Mike Seymour; 23.03.2010
comment
@Mike Seymour, к сожалению, упустил из виду это преимущество, как вы заявили, однако, большинство людей, которые задают этот вопрос, стремятся принудительно встроить функцию. - person marr75; 23.03.2010

Стандарт ISO C++ 2003 года гласит:

7.1.2/2 Объявление функции (8.3.5, 9.3, 11.4) со встроенным спецификатором объявляет встроенную функцию. Спецификатор inline указывает реализации, что встроенная подстановка тела функции в точке вызова предпочтительнее обычного механизма вызова функции
. Реализация не требуется для выполнения этой встроенной
подстановки в точке вызова; однако, даже если эта inline
замена опущена, другие правила для встроенных функций, определенные в 7.1.2, все равно должны соблюдаться.

7.1.2/3 Функция, определенная в определении класса, является встроенной
функцией. Спецификатор inline не должен появляться в объявлении функции области блока.

7.1.2/4 Встроенная функция должна быть определена в каждой единице перевода
, в которой она используется, и должна иметь точно такое же определение в каждом случае
(3.2). [Примечание: вызов встроенной функции может быть обнаружен
до того, как ее определение появится в единице перевода. ] Если функция с внешней связью объявлена ​​встроенной в одной единице перевода, она должна быть объявлена ​​встроенной во всех единицах перевода, в которых она появляется; диагностика не требуется. Встроенная функция с внешней связью должна иметь один и тот же адрес во всех единицах перевода. Статическая локальная переменная во внешней функции inline
всегда ссылается на один и тот же объект. Строковый литерал во встроенной
внешней функции представляет собой один и тот же объект в разных единицах перевода
.

person Alexandros Gezerlis    schedule 23.03.2010
comment
Это самый полезный ответ, но он также вводит в заблуждение, как правило, основные компиляторы сосредотачиваются на реализации, не требующей выполнения этого встроенного элемента замены стандарта. - person marr75; 23.03.2010

Есть две вещи, которые не следует смешивать вместе:

  1. Как вы помечаете функцию как встроенную: определяете ее как встроенную перед сигнатурой или определяете ее в точке объявления;
  2. Что компилятор будет обрабатывать такую ​​встроенную маркировку: независимо от того, как вы пометили функцию как встроенную, она будет рассматриваться компилятором как запрос.
person Eugen Constantin Dinca    schedule 23.03.2010