用相同类型的新对象覆盖对象并使用闭包
Overwriting object with new object of same type and using closure using this
在下面的代码中,一个对象被相同类型的新对象覆盖,其中 lambda 表达式创建了一个使用旧对象的 this
的闭包。旧地址 (this
) 保持不变,新对象具有相同的布局,所以这应该没问题,而不是 UB。但是对于非平凡的对象或其他情况呢?
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();
}
您实际上并没有替换任何对象。您只是将另一个对象 分配 到当前对象。 o =
只是调用隐式复制赋值运算符,它将 copy-assign 来自临时 A
的各个成员,这些临时 A
在 A{...}
.
的赋值表达式中构造
lambda 将从 g
中的 this
捕获 this
,而不是从临时对象。
std::function
将始终保留一份 lambda 的副本,该副本引用调用 g
的原始对象,因为这是它的父对象,它不能比它长。
所以这里没有问题。唯一的例外是您在销毁 A
对象期间调用 f
,在这种情况下,可能会禁止使用捕获的指针。
这里是一个稍微修改过的代码,带有一个特殊情况。我在一个函数中创建了一个临时对象并在其上调用 g
并向其传递一个更 永久 的对象。临时对象消失了,长寿命对象现在有一个闭包,指向生命结束后的对象。调用 f
是 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
}
输出为:
1
dtor3 0135F9C0
dtor2 0135FA30
3
f341072 0135FA30
dtor3 0135FAA8
证明 a.f()
的调用试图在地址 0135FA30(在那个特定的 运行 中)被销毁后使用该对象。
在下面的代码中,一个对象被相同类型的新对象覆盖,其中 lambda 表达式创建了一个使用旧对象的 this
的闭包。旧地址 (this
) 保持不变,新对象具有相同的布局,所以这应该没问题,而不是 UB。但是对于非平凡的对象或其他情况呢?
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();
}
您实际上并没有替换任何对象。您只是将另一个对象 分配 到当前对象。 o =
只是调用隐式复制赋值运算符,它将 copy-assign 来自临时 A
的各个成员,这些临时 A
在 A{...}
.
lambda 将从 g
中的 this
捕获 this
,而不是从临时对象。
std::function
将始终保留一份 lambda 的副本,该副本引用调用 g
的原始对象,因为这是它的父对象,它不能比它长。
所以这里没有问题。唯一的例外是您在销毁 A
对象期间调用 f
,在这种情况下,可能会禁止使用捕获的指针。
这里是一个稍微修改过的代码,带有一个特殊情况。我在一个函数中创建了一个临时对象并在其上调用 g
并向其传递一个更 永久 的对象。临时对象消失了,长寿命对象现在有一个闭包,指向生命结束后的对象。调用 f
是 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
}
输出为:
1
dtor3 0135F9C0
dtor2 0135FA30
3
f341072 0135FA30
dtor3 0135FAA8
证明 a.f()
的调用试图在地址 0135FA30(在那个特定的 运行 中)被销毁后使用该对象。