In the following code an object is overwritten with a new object of same type, where a lambda-expression creates a closure that uses this
of the old object. The old address (this
) remains the same, the new object has the same layout, so this should be ok and not UB. But what about non trivial objects or other cases?
struct A {
void g(A& o, int v) {
o = A{.x = v, .f = [this]{
std::cout << "f" << this->x << '\n';
}};
}
int x{0};
std::function<void()> f;
~A() {
std::cout << "dtor" << x << '\n';
}
};
void test() {
A a;
a.g(a, 2);
a.f();
}
CodePudding user response:
You are not actually replacing any object. You are just assigning from another object to the current one. o =
simply calls the implicit copy assignment operator which will copy-assign the individual members from the temporary A
constructed in the assignment expression with A{...}
.
The lambda is going to capture this
from this
in g
, not from the temporary object.
std::function
will always keep a copy of the lambda referring to the original object on which g
was called and since that is its parent object, it cannot outlive it.
So there is no problem here. The only exception would be that you call f
during the destruction of the A
object, in which case using the captured pointer may be forbidden.
CodePudding user response:
Here is a slightly modified code with a corner case. I create a temporary in a function and call g
on it passing it a more permanent object. The temporary vanishes and the long life object now has a closure refering to an object after its end of life. Invoking f
is UB:
#include <iostream>
#include <functional>
struct A {
void g(A& o, int v) {
o = A{ .x = v, .f = [this] {
std::cout << "f" << this->x << ' ' << this << '\n';
} };
}
int x{ 0 };
std::function<void()> f;
~A() {
std::cout << "dtor" << x << ' ' << this << '\n';
}
};
void test(A& a) {
A b{ 2 };
b.g(a, 3);
}
int main() {
A a{ 1 };
std::cout << a.x << '\n';
test(a);
std::cout << a.x << '\n';
a.f(); // UB because a.f uses an object after its end of life
}
The output is:
1
dtor3 0135F9C0
dtor2 0135FA30
3
f341072 0135FA30
dtor3 0135FAA8
proving that the invocation of a.f()
tried to use the object at address 0135FA30 (in that specific run) after it has been destroyed.