void do_something(std::function<void(void)> callback){
callback();
}
int main() {
int counter = 0;
while(counter < 10000000) {
do_something([&](){ std::cout << "by reference:" << counter << endl; });
do_something([=](){ std::cout << "by value:" << counter << endl; });
++counter;
}
}
Как показано в приведенном выше коде, будет ли создаваться новая лямбда в каждом цикле? Будет ли разница с захватом значения или ссылки?
Я искал некоторые статьи, но я все еще не совсем уверен. Я считаю, что лямбда-выражения для захвата значения должны создаваться несколько раз, а для захвата ссылок, вероятно, нет.
Если лямбда, захваченная по ссылке, может быть повторно использована, произойдет ли копирование лямбды? Например, будут ли по-прежнему иметь значение следующие две функции?
void do_something2(std::function<void(void)> callback){
callback();
}
void do_something2(std::function<void(void)> & callback){
callback();
}
Это зависит от того, что вы имеете в виду, определение лямбда создается только один раз. Экземпляр объекта лямбды создается много раз. Попробуйте c++ Insights это может дать вам много интересной информации. Например, в вашем случае:
while(counter < 5) {
class __lambda_16_20
{
public:
inline /*constexpr */ void operator()() const
{
std::operator<<(std::cout, "by reference:").operator<<(counter).operator<<(std::endl);
}
private:
int & counter;
public:
// inline /*constexpr */ __lambda_16_20(const __lambda_16_20 &) noexcept = default;
// inline /*constexpr */ __lambda_16_20(__lambda_16_20 &&) noexcept = default;
__lambda_16_20(int & _counter)
: counter{_counter}
{}
};
do_something(std::function<void ()>(__lambda_16_20{counter}));
Вы можете видеть, что определение лямбды генерируется внутри while, но только 1 раз. Экземпляр объекта (в данном случае __lambda_16_20
создается каждый раз, когда вы проходите через цикл.
В вашем примере с:
void do_something2(std::function<void(void)> & callback)
да, это изменит дело. Поскольку вы запрашиваете неконстантную ссылку, это значение больше не может быть временным. Что означает это:
do_something2([=](){ std::cout << "by value:" << counter << endl; });
Не компилируется. Вместо этого вам понадобится изменяемое значение:
auto my_lambda = [=](){ std::cout << "by value:" << counter << endl; };
do_something(my_lambda{});
Делаете ли вы my_lambda
в цикле или вне цикла, зависит от вас. Затем вам нужно будет дважды проверить, как вы используете захват по ссылке и захват по значению.
Кстати, если мы убедимся, что обратный вызов захватывает лямбда по ссылке, всегда используйте "void do_something(std::function<void(void)> & callback)" будет лучше, чем "void do_something(std::function<void( void)> обратный вызов)"?
Для Q1 это так, но какова «стоимость создания лямбды». Убедитесь, что вы избегаете преждевременной оптимизации, потому что вы можете сделать свой код более сложным для понимания, а результаты профилирования могут вас удивить. Непрерывная передача ссылки может быть медленнее, чем передача по значению, потому что ваш компилятор может просто оптимизировать все, что нужно сделать, чтобы это убрать.
Что значит «лучше» для Q2? Читабельнее? Конечно нет. Быстрее? Может быть, а может и нет, вам нужно будет профилировать реальный пример.
@FantasticMrFox Предлагаемое вами изменение неконстантного эталонного случая не решает его, потому что требуется ссылка на std::function<...>
. В предложенном коде все еще есть преобразование из лямбды в std::function<...>
, которое создает временный объект, который не может привязываться к неконстантной ссылке.
@ j6t правда, но я не предлагал это решение. ОП спросил об этом случае. Мое предлагаемое решение состояло бы в том, чтобы использовать лямбда как есть, компилятор оптимизирует это, и это вряд ли будет источником каких-либо реальных узких мест.
Если экземпляры создаются в каждом цикле для захвата лямбда-выражения по ссылке, позволяет ли вытягивание определения лямбда-выражения за пределы цикла избежать затрат на создание?