Что происходит, если лямбда перемещается/разрушается во время работы?
Рассмотрим:
std::vector<std::function<void()>> vec;
something_unmovable m;
vec.push_back([&vec, m]() {
vec.resize(100);
// things with 'm'
});
vec[0]();
vec.resize(100)
, вероятно, вызовет перераспределение вектора, что означает, что std::function
будет скопирован в новое место, а старые уничтожены. Но это происходит, пока старый все еще работает. Этот конкретный код работает, потому что лямбда ничего не делает, но я думаю, что это может легко привести к поведению undefined.
Итак, что именно происходит? Доступно ли m
из вектора? Или это то, что указатель this
лямбда теперь недействителен (указывает на освобожденную память), поэтому никакие захваты лямбда не могут быть доступны, но если он запускает код, который не использует ничего, что он захватывает, это не undefined поведение?
Кроме того, в том случае, когда лямбда может быть перемещена любым другим?
Ответы
Ответ 1
Как уже упоминалось в других ответах, лямбда - это, по сути, синтаксический сахар для легкого создания типов, которые предоставляют пользовательскую реализацию operator()
. Вот почему вы можете даже писать лямбда-вызовы, используя явную ссылку на operator()
, например: int main() {return []() {return 0; }.operator()(); }Код>. Те же правила для всех нестатических функций-членов также применяются к лямбда-телам.
И эти правила позволяют уничтожать объект во время выполнения функции-члена, если функция-член не использует this
. Ваш пример необычный, более распространенным примером является нестатическая функция-член, выполняющая delete this;
. Это привело к часто задаваемым вопросам С++, объяснив, что это разрешено.
Стандарт позволяет это, не обращаясь к нему, насколько мне известно. Он описывает семантику функций-членов таким образом, который не полагается на объект, который не уничтожается, поэтому реализации должны быть уверены, что функции-члены продолжают выполняться, даже если объекты будут уничтожены.
Итак, чтобы ответить на ваши вопросы:
Или это то, что этот указатель лямбды теперь недействителен (указывает на освобожденную память), поэтому никакие захваты лямбда не могут быть доступны, но если он запускает код, который не использует ничего, что он захватывает, это не undefined поведение?
Да, в значительной степени.
Кроме того, в том случае, когда лямбда может быть перемещена любым другим?
Нет, это не так.
Единственный раз, когда возможно движение лямбда, возможно, имеет значение после перемещения лямбда. В вашем примере оператор ()
продолжает выполнение на исходном перемещенном и затем уничтоженном функторе.
Ответ 2
Вы можете обрабатывать лямбда-захваты, как обычные экземпляры структуры.
В вашем случае:
struct lambda_UUID_HERE_stuff
{
std::vector<std::function<void()>> &vec;
something_unmovable m;
void operator()()
{
this->vec.resize(100);
}
};
... и я считаю, что применяются все те же правила (что касается VS2013).
Итак, это, по-видимому, еще один случай поведения undefined. То есть, если &vec
указывает на вектор, содержащий экземпляр захвата, а операции внутри operator()
вызывают изменение этого вектора.
Ответ 3
В конечном счете, в этом вопросе много деталей, которые не имеют отношения к делу. Мы можем свести его к вопросу о действительности:
struct A {
something_unmovable m;
void operator()() {
delete this;
// do something with m
}
};
И спросите об этом. В конце концов, влияние resize()
заключается в вызове деструктора объекта mid-function-call. Независимо от того, было ли оно перемещено - из или скопировано - из std::vector
не имеет значения - в любом случае оно впоследствии будет уничтожено.
Стандарт сообщает нам в [class.cdtor], что:
Для объекта с нетривиальным деструктор, ссылаясь на любой нестатический член или базовый класс объекта после завершения деструктора выполнение приводит к поведению undefined.
Итак, если деструктор something_unmovable
является нетривиальным (что сделает деструктор A
- или ваш лямбда - нетривиальным), любая ссылка на m
после вызова деструктора undefined. Если something_unmovable
имеет тривиальный деструктор, то ваш код вполне приемлем. Если вы не сделаете ничего после delete this
(resize()
в своем вопросе), тогда это совершенно правильное поведение.
Доступно ли m из вектора?
Да, функтор в vec[0]
по-прежнему будет иметь m
. Это может быть оригинальная лямбда - или это может быть копия оригинальной лямбды. Но будет m
так или иначе.
Ответ 4
Объекты функций обычно могут копироваться, поэтому ваша лямбда будет продолжать работать без какого-либо эффекта. Если он фиксирует по ссылке AFAIR, внутренняя реализация будет использовать std:: reference_wrapper, чтобы лямбда оставалась скопированной.