Consider:
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)
will probably cause a re-allocation of the vector, which means that the std::function
s will be copied to a new location, and the old ones destroyed. Yet this happens while the old one is still running. This particular code runs because the lambda doesn't do anything, but I imagine this can easily result in undefined behavior.
So, what happens exactly? Is m
still accessible from the vector? Or is it that the this
pointer of the lambda is now invalid (points to freed memory), so nothing the lambda captures can be accessible, yet if it runs code that doesn't use anything it captures, it's not undefined behavior?
Also, is the case where the lambda is movable any different?
Function objects are normally copyable, so your lambda would continue to run without ill effect. If it captures by reference AFAIR the internal implementation will use std::reference_wrapper so that the lambda remains copyable.
Ultimately, there are a lot of details in this question that aren't relevant. We can reduce it to asking about the validity of:
And ask about that behavior. After all, the impact of the
resize()
is to call the destructor of the object mid-function-call. Whether it's moved-from or copied-from bystd::vector
doesn't matter - either way it will subsequently be destroyed.The standard tells us in [class.cdtor] that:
So if the destructor of
something_unmovable
is non-trivial (which would make the destructor ofA
-- or your lambda -- non-trivial), any reference tom
after the destructor is called is undefined behavior. Ifsomething_unmovable
does have a trivial destructor, then your code is perfectly acceptable. If you don't do anything after thedelete this
(theresize()
in your question), then it's perfectly valid behavior.Yes, the functor in
vec[0]
will still havem
in it. It may be the original lambda - or it may be a copy of the original lambda. But there will be anm
one way or the other.As already covered by other answers, lambdas are essentially syntactic sugar for easily creating types that provide a custom
operator()
implementation. This is why you can even write lambda invocations using an explicit reference tooperator()
, like so:int main() { return [](){ return 0; }.operator()(); }
. The same rules for all non-static member functions also apply to lambda bodies.And those rules allow the object being destroyed while the member function is being executed, so long as the member function does not use
this
afterwards. Your example is an unusual one, the more common example is for a non-static member function executingdelete this;
. This made it into the C++ FAQ, explaining that it's allowed.The standard allows this by not really addressing it, as far as I am aware. It describes the semantics of member functions in a way that doesn't rely on the object not being destroyed, so implementations must make sure to let member functions continue executing even if the objects get destroyed.
So to answer your questions:
Yes, pretty much.
No, it's not.
The only time where the lambda being movable could possibly matter is after the lambda has been moved. In your example, the
operator()
continues executing on the original moved-from and then destroyed functor.You can treat lambda captures like ordinary struct instances.
In your case:
...and I believe all the same rules apply (as far as VS2013 is concerned).
So, this appears to be another case of undefined behavior. That is, if
&vec
happens to point to the vector containing the capture instance, and the operations withinoperator()
cause that vector to resize.