Ответ 1
Отказ от ответственности: мой ответ несколько упрощен по сравнению с реальностью (я отложил некоторые детали), но большая картина здесь. Кроме того, в Стандарте не указано полностью, как lambdas или std::function
должны быть реализованы внутренне (реализация имеет некоторую свободу), поэтому, как и любое обсуждение деталей реализации, ваш компилятор может или не может сделать это именно таким образом.
Но опять-таки это тема, очень похожая на VTables: стандарт не требует многого, но любой разумный компилятор по-прежнему вполне может сделать это таким образом, поэтому я считаю, что стоит немного вникать в это.:)
Лямбда
Самый простой способ реализовать лямбда - это анонимный struct
:
auto lambda = [](Args...) -> Return { /*...*/ };
// roughly equivalent to:
struct {
Return operator ()(Args...) { /*...*/ }
}
lambda; // instance of the anonymous struct
Как и любой другой класс, когда вы передаете свои экземпляры, вам никогда не придется копировать код, а только фактические данные (здесь, вообще ничего).
Объекты, захваченные по значению, копируются в struct
:
Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };
// roughly equivalent to:
struct Temporary { // note: we can't make it an anonymous struct any more since we need
// a constructor, but that just a syntax quirk
const Value v; // note: capture by value is const by default unless the lambda is mutable
Temporary(Value v_) : v(v_) {}
Return operator ()(Args...) { /*... use v, captured by value...*/ }
}
lambda(v); // instance of the struct
Опять же, передача его только означает, что вы передаете данные (v
), а не сам код.
Аналогично, объекты, захваченные ссылкой, ссылаются на struct
:
Value v;
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ };
// roughly equivalent to:
struct Temporary {
Value& v; // note: capture by reference is non-const
Temporary(Value& v_) : v(v_) {}
Return operator ()(Args...) { /*... use v, captured by reference...*/ }
}
lambda(v); // instance of the struct
Это почти все, когда речь заходит о самих лямбда (за исключением нескольких деталей реализации, которые я пропустил, но которые не имеют отношения к пониманию того, как это работает).
std::function
std::function
является общей оболочкой любого функтора (lambdas, автономные/статические/членные функции, классы-функторы, такие как те, которые я показал,...).
Внутренние элементы std::function
довольно сложны, потому что они должны поддерживать все эти случаи. В зависимости от точного типа функтора для этого требуется, по крайней мере, следующие данные (указать или принять детали реализации):
- Указатель на автономную/статическую функцию.
Или
- Указатель на копию [см. примечание ниже] функтора (динамически выделенного для того, чтобы разрешить любой тип функтора, как вы его правильно отметили).
- Указатель на вызываемую функцию-член.
- Указатель на распределитель, который может копировать функтор и сам (поскольку любой тип функтора можно использовать, указатель-функтор должен быть
void*
и, следовательно, должен быть такой механизм - возможно, используя полиморфизм, например, базовый класс + виртуальные методы, производный класс создается локально в конструкторахtemplate<class Functor> function(Functor)
).
Поскольку он заранее не знает, какой тип функтора он должен будет хранить (и это становится очевидным из-за того, что std::function
можно переназначить), тогда он должен справиться со всеми возможными случаями и принять решение в во время выполнения.
Примечание. Я не знаю, где Стандарт обязывает его, но это определенно новая копия, базовый функтор не используется:
int v = 0;
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; };
std::function<void()> g = f;
f(); // 0
f(); // 1
g(); // 0
g(); // 1
Итак, когда вы проходите std::function
вокруг, он включает по крайней мере те четыре указателя (и действительно на GCC 4.7. 64 бит sizeof(std::function<void()>
равно 32, которые представляют собой четыре указателя на 64 бита) и, возможно, динамически выделенную копию функтора ( который, как я уже сказал, содержит только захваченные объекты, вы не копируете код).
Ответьте на вопрос
Какова стоимость передачи лямбда для такой функции? [контекст вопроса: по значению]
Хорошо, как вы можете видеть, это зависит в основном от вашего функтора (либо от функционала struct
, либо от lambda), и от переменных, которые он содержит. Накладные расходы по сравнению с непосредственным прохождением функтора struct
по величине весьма незначительны, но, конечно, намного выше, чем передача функтора struct
по ссылке.
Должен ли я отмечать каждый объект функции, переданный с помощью
const&
, чтобы не была сделана копия?
Я боюсь, что это очень сложно ответить общим способом. Иногда вам нужно передать ссылку const
, иногда по значению, иногда по ссылке rvalue, чтобы вы могли ее переместить. Это действительно зависит от семантики вашего кода.
Правила, по которым вы должны выбрать, - это совершенно другая тема IMO, просто помните, что они такие же, как для любого другого объекта.
В любом случае, теперь у вас есть все ключи для принятия обоснованного решения (опять же, в зависимости от вашего кода и его семантики).