При первом знакомстве с лямбда-выражением у многих возникает смутное впечатление, что для создания этих функций происходит какое-то волшебство во время выполнения. В частности, если у вас есть функция, которая возвращает вновь созданную функцию в качестве результата, может показаться, что возвращаемая функция «создается» каждый раз, когда вызывается окружающая функция. Но это неправильно - лямбда-выражение (и это верно для любого языка) содержит некоторый код, который можно скомпилировать так же, как и любой другой код, и все это происходит статически, без каких-либо затрат , которые должны быть осталось на время выполнения.
Единственная проблема заключается в том, что происходит с переменными, которые закрываются, но это не исключает такой компиляции - чтобы создать замыкание, вы просто соединяете данные замыкания (эти переменные) со статически указателем на скомпилированный код. Смысл этого с точки зрения производительности заключается в том, что не должно быть потери производительности вообще - закрытых переменных или нет. Даже с закрытыми переменными нет затрат - любой другой способ решения любой проблемы, с которой вы сталкиваетесь, потребует некоторой упаковки этих значений, поэтому стоимость выделения будет одинаковой независимо от того, как вы ее храните (явно или неявно в замкнутых над переменными). Если альтернативному решению не нужно упаковывать некоторые значения, тогда не будет необходимости закрывать их с помощью замыканий. Это действительно то же самое, что и с локальным распределением, необходимым для выполнения кода - что, очевидно, будет одинаковым, независимо от того, происходит ли код из замыкания с его локальной областью или из какой-то другой области, которая будет нуждаться в локальном состоянии того же типа.
Опять же, это все, что есть в любом языке с замыканиями, и у кода C ++ нет причин страдать от некоторых проблем с производительностью, в отличие от других языков. Одна странность в лямбда-выражениях C ++ заключается в необходимости указывать, какие переменные вы закрываете, тогда как в большинстве других языков вы просто закрываете все по умолчанию. Казалось бы, это дает C ++-коду некоторое преимущество в том, что он имеет больший контроль над тем, сколько вещей должно быть в пакетах с замыканием - но это то, что компилятору очень легко сделать автоматически, без явных аннотаций. Это приводит к одной из самых распространенных вещей, которые делают компиляторы функциональных языков - «лямбда-лифтинг» - когда функция эффективно поднимается до верхнего уровня, избегая необходимости создавать замыкания во время выполнения, если они не нужны. Например, если вы пишете (используя некоторый псевдокод, подобный JS):
function foo(x) {
return function(y) { return y+3; }
}
тогда легко (как для компилятора, так и для человека) увидеть, что возвращаемая функция не зависит от x
, и теперь компилятор может ее поднять, как если бы вы написали:
function generated_name1234(y) { return y+3; }
function foo(x) {
return generated_name1234;
}
Подобные методы используются, когда закрываются только некоторые значения.
Но это расходится. Суть в том, что лямбда-выражения не являются чем-то, что влечет за собой снижение производительности - закрытые переменные или нет.
(Что касается сравнения лямбда-выражений с использованием operator()
, я не уверен, что будет делать большинство компиляторов C ++, но лямбда-выражения должны быть быстрее, поскольку не требуется диспетчеризация во время вызова любого метода. Даже если лямбда-выражения реализованные как анонимные классы с оператором ()
, вышеупомянутые методы могут применяться и в этом случае, что означает, что механизм диспетчеризации может быть скомпилирован, что означает, что у него не должно быть дополнительных затрат, что делает его похожим на специальный случай, когда анонимный класс тривиален до точки эффективной компиляции.)