Каково время жизни выражения лямбда С++?
(Я прочитал Что такое время жизни лямбда-производных неявных функторов в С++? уже и не отвечает на этот вопрос.)
Я понимаю, что синтаксис лямбда С++ - это просто сахара, чтобы создать экземпляр анонимного класса с оператором вызова и некоторым состоянием, и я понимаю требования к жизни этого состояния (определяется ли ваш захват по значению по ссылке). Но каково время жизни самого лямбда-объекта? В следующем примере возвращается экземпляр std::function
, который будет полезен?
std::function<int(int)> meta_add(int x) {
auto add = [x](int y) { return x + y; };
return add;
}
Если это так, как это работает? Мне кажется, что это слишком много волшебства - я могу только представить, что он работает с копированием всего моего экземпляра, который может быть очень тяжелым в зависимости от того, что я захватил, - в прошлом я использовал std::function
в основном с голой функцией указатели и скопировать их быстро. Это также кажется проблематичным в свете стирания типа std::function
.
Ответы
Ответ 1
Время жизни - это именно то, что было бы, если бы вы заменили вашу лямбду ручным функтором:
struct lambda {
lambda(int x) : x(x) { }
int operator ()(int y) { return x + y; }
private:
int x;
};
std::function<int(int)> meta_add(int x) {
lambda add(x);
return add;
}
Объект будет создан локально для функции meta_add
, а затем перенесен [в его entirty, включая значение x
] в возвращаемое значение, тогда локальный экземпляр выйдет за пределы области действия и будет уничтожен как нормальный. Но объект, возвращаемый функцией, останется в силе до тех пор, пока объект std::function
, который его поддерживает. Как долго это очевидно зависит от вызывающего контекста.
Ответ 2
Кажется, вы более смущены std::function
, чем lambdas.
std::function
использует метод, называемый стиранием типа. Здесь быстро летать.
class Base
{
virtual ~Base() {}
virtual int call( float ) =0;
};
template< typename T>
class Eraser : public Base
{
public:
Eraser( T t ) : m_t(t) { }
virtual int call( float f ) override { return m_t(f); }
private:
T m_t;
};
class Erased
{
public:
template<typename T>
Erased( T t ) : m_erased( new Eraser<T>(t) ) { }
int do_call( float f )
{
return m_erased->call( f );
}
private:
Base* m_erased;
};
Почему вы хотите удалить тип? Не тот тип, который мы хотим просто int (*)(float)
?
То, что позволяет стирание типа, Erased
теперь может хранить любое значение, которое может быть вызвано как int(float)
.
int boring( float f);
short interesting( double d );
struct Powerful
{
int operator() ( float );
};
Erased e_boring( &boring );
Erased e_interesting( &interesting );
Erased e_powerful( Powerful() );
Erased e_useful( []( float f ) { return 42; } );
Ответ 3
Это:
[x](int y) { return x + y; };
Является эквивалентным: (или может считаться слишком)
struct MyLambda
{
MyLambda(int x): x(x) {}
int operator()(int y) const { return x + y; }
private:
int x;
};
Таким образом, ваш объект возвращает объект, который выглядит именно так. У которого есть четко определенный конструктор копирования. Поэтому представляется разумным, что он может быть правильно скопирован из функции.
Ответ 4
В опубликованном вами коде:
std::function<int(int)> meta_add(int x) {
auto add = [x](int y) { return x + y; };
return add;
}
Объект std::function<int(int)>
, возвращаемый функцией, фактически содержит перемещенный экземпляр объекта функции лямбда, который был назначен локальной переменной add
.
Когда вы определяете lambda С++ 11, который захватывает по значению или по ссылке, компилятор С++ автоматически генерирует уникальный функциональный тип, экземпляр которого создается, когда лямбда вызывается или назначается переменной. Чтобы проиллюстрировать, ваш компилятор С++ может генерировать следующий тип класса для лямбда, определенного [x](int y) { return x + y; }
:
class __lambda_373s27a
{
int x;
public:
__lambda_373s27a(int x_)
: x(x_)
{
}
int operator()(int y) const {
return x + y;
}
};
Затем функция meta_add
по существу эквивалентна:
std::function<int(int)> meta_add(int x) {
__lambda_373s27a add = __lambda_373s27a(x);
return add;
}
EDIT:. Кстати, я не уверен, знаете ли вы это, но это пример функции currying в С++ 11.