Детальная разница между вызовом функции и вызовом функции?

Основная причина этого заключается в том, что for_each() фактически не предполагает его третий аргумент - функция. Он просто предполагает, что его третий аргумент - это то, что может быть вызываемый с соответствующим аргументом. подходящий объект также служит как - и часто лучше, чем - функция. Например, легче inline оператор приложения класс, чем встроить переданную функцию как указатель на функцию. Следовательно, объекты функции часто выполнять быстрее, чем обычные функции. Объект класса с оператор приложения (§11.9) называемый функциональным объектом, функтор или просто объект функции.

[Stroustrup, С++ 3rd edition, 18.4-last paragraph]

  • Я всегда думал, что оператор (), как вызов функции во время выполнения. как он отличается от вызов нормальной функции?

  • Почему проще встраивать   оператора приложения, чем обычный   функция?

  • Как они быстрее, чем функция   позвонить?

Ответы

Ответ 1

Как правило, функторы передаются в шаблонные функции - если вы это делаете, то не имеет значения, передаете ли вы "реальную" функцию (то есть указатель функции) или функтор (т.е. класс с перегруженным operator()). По сути, оба имеют оператор вызова функции и, следовательно, являются допустимыми параметрами шаблона, для которых компилятор может создать экземпляр шаблона for_each. Это означает, что for_each создается либо с конкретным типом переданного функтора, либо с переданным конкретным типом переданного указателя функции. И это в той специализации, что функторы могут превосходить указатели на функции.

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

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

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

Резюме

Указатели функций не могут быть встроены, поскольку при компиляции for_each компилятор имеет только тип функция, а не тождество функции. Напротив, функторы могут быть встроены, поскольку, хотя компилятор имеет только тип функтора, типа обычно достаточно однозначно идентифицировать метод функтора operator().

Ответ 2

Рассмотрим два следующих экземпляра шаблона:

std::for_each<class std::vector<int>::const_iterator, class Functor>(...)

и

std::for_each<class std::vector<int>::const_iterator, void(*)(int)>(...)

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

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

Теперь интеллектуальные компиляторы могут определить, какую функцию вызывать во время компиляции, особенно в таких сценариях:

std::for_each(v.begin(), v.end(), &foo);

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

Ответ 3

Я всегда думал, что вызов operator() аналогичен вызову функции во время выполнения. как он отличается от обычного вызова функции?

Моя догадка не очень. Для доказательства этого посмотрите на сборку сборки компилятора для каждого. Предполагая такой же уровень оптимизации, он, вероятно, будет почти идентичным. (С дополнительной деталью, которую должен пройти передатчик this.)

Почему проще встраивать оператор приложения, чем нормальную?

Чтобы процитировать рекламный ролик, который вы указали:

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

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

Как они быстрее, чем вызов функции?

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