为什么这个 lambda [=] 捕获会创建多个副本?
Why does this lambda [=] capture create several copies?
在下面的代码中:
#include <iostream>
#include <thread>
using namespace std;
class tester {
public:
tester() {
cout << "constructor\t" << this << "\n";
}
tester(const tester& other) {
cout << "copy cons.\t" << this << "\n";
}
~tester() {
cout << "destructor\t" << this << "\n";
}
void print() const {
cout << "print\t\t" << this << "\n";
}
};
int main() {
tester t;
cout << " before lambda\n";
thread t2([=] {
cout << " thread start\n";
t.print();
cout << " thread end\n";
});
t2.join();
cout << " after join" << endl;
return 0;
}
使用 cl.exe
编译时(在 Windows 上)我得到以下信息:
constructor 012FFA93
before lambda
copy cons. 012FFA92
copy cons. 014F6318
destructor 012FFA92
thread start
print 014F6318
thread end
destructor 014F6318
after join
destructor 012FFA93
使用 g++
(在 WSL 上)我得到:
constructor 0x7ffff5b2155e
before lambda
copy cons. 0x7ffff5b2155f
copy cons. 0x7ffff5b21517
copy cons. 0x7fffedc630c8
destructor 0x7ffff5b21517
destructor 0x7ffff5b2155f
thread start
print 0x7fffedc630c8
thread end
destructor 0x7fffedc630c8
after join
destructor 0x7ffff5b2155e
我预计 [=]
捕获会创建 tester
的 1 个副本。为什么有好几份立即销毁?
为什么MSVC和GCC会有分歧?这是未定义的行为还是什么?
标准要求传递给 std::thread
的构造函数的可调用对象是可有效复制构造的 ([thread.thread.constr])
Mandates: The following are all true:
is_constructible_v<decay_t<F>, F>
- [...]
is_constructible_v<decay_t<F>, F>
与 is_copy_constructible
相同(或者相反,相反)。
这是为了允许实现自由传递可调用对象,直到它到达被调用的位置。 (事实上,标准本身建议至少复制一次可调用对象。)
由于 lambda 被编译成一个小的 class 并重载了函数调用运算符(functor),每次复制 lambda 时,它都会创建一个捕获的 tester
个实例的副本。
如果您不希望复制发生,您可以在捕获列表中引用您的实例:
thread t2([&ref = t] {
cout << " thread start\n";
ref.print();
cout << " thread end\n";
});
Live Demo
输出:
constructor 0x7ffdfdf9d1e8
before lambda
thread start
print 0x7ffdfdf9d1e8
thread end
after join
destructor 0x7ffdfdf9d1e8
在下面的代码中:
#include <iostream>
#include <thread>
using namespace std;
class tester {
public:
tester() {
cout << "constructor\t" << this << "\n";
}
tester(const tester& other) {
cout << "copy cons.\t" << this << "\n";
}
~tester() {
cout << "destructor\t" << this << "\n";
}
void print() const {
cout << "print\t\t" << this << "\n";
}
};
int main() {
tester t;
cout << " before lambda\n";
thread t2([=] {
cout << " thread start\n";
t.print();
cout << " thread end\n";
});
t2.join();
cout << " after join" << endl;
return 0;
}
使用 cl.exe
编译时(在 Windows 上)我得到以下信息:
constructor 012FFA93
before lambda
copy cons. 012FFA92
copy cons. 014F6318
destructor 012FFA92
thread start
print 014F6318
thread end
destructor 014F6318
after join
destructor 012FFA93
使用 g++
(在 WSL 上)我得到:
constructor 0x7ffff5b2155e
before lambda
copy cons. 0x7ffff5b2155f
copy cons. 0x7ffff5b21517
copy cons. 0x7fffedc630c8
destructor 0x7ffff5b21517
destructor 0x7ffff5b2155f
thread start
print 0x7fffedc630c8
thread end
destructor 0x7fffedc630c8
after join
destructor 0x7ffff5b2155e
我预计
[=]
捕获会创建tester
的 1 个副本。为什么有好几份立即销毁?为什么MSVC和GCC会有分歧?这是未定义的行为还是什么?
标准要求传递给 std::thread
的构造函数的可调用对象是可有效复制构造的 ([thread.thread.constr])
Mandates: The following are all true:
is_constructible_v<decay_t<F>, F>
- [...]
is_constructible_v<decay_t<F>, F>
与 is_copy_constructible
相同(或者相反,相反)。
这是为了允许实现自由传递可调用对象,直到它到达被调用的位置。 (事实上,标准本身建议至少复制一次可调用对象。)
由于 lambda 被编译成一个小的 class 并重载了函数调用运算符(functor),每次复制 lambda 时,它都会创建一个捕获的 tester
个实例的副本。
如果您不希望复制发生,您可以在捕获列表中引用您的实例:
thread t2([&ref = t] {
cout << " thread start\n";
ref.print();
cout << " thread end\n";
});
Live Demo
输出:
constructor 0x7ffdfdf9d1e8
before lambda
thread start
print 0x7ffdfdf9d1e8
thread end
after join
destructor 0x7ffdfdf9d1e8