使用函数对象构造线程时拷贝构造函数被调用两次的原因咨询
为什么用函数对象构造std::thread时拷贝构造函数会被调用两次?
兄弟我太懂你的困惑了!当初第一次碰到这种情况的时候,我盯着控制台输出的两行拷贝构造日志,差点把键盘敲碎——明明只传了一个对象,为啥平白多拷贝一次?这第二次拷贝看起来完全多余啊!别着急,咱们一点点把这事说透。
首先先把你的代码补全(方便大家理解完整场景):
#include <iostream> #include <thread> using namespace std; struct A { A() { cout << "constructor this=" << this << " thread_id=" << this_thread::get_id() << endl; } A(const A &other) { cout << "Copy constructor this=" << this << " thread_id=" << this_thread::get_id() << endl; } void operator()() const { cout << "Thread executing, this=" << this << " thread_id=" << this_thread::get_id() << endl; } }; int main() { A a; thread t(a); t.join(); return 0; }
两次拷贝到底是怎么来的?
咱们拆解每一步:
- 第一次拷贝(主线程中):当你把
a传给std::thread的构造函数时,标准库会先把这个对象拷贝到std::thread自身的内部存储区域里。这一步是必须的——目的是确保线程拥有自己独立的对象副本,避免主线程里的a被提前销毁或者意外修改,导致线程执行时出现未定义行为。 - 第二次拷贝(子线程中):这一步是在线程开始执行的时候,把
std::thread内部存储的那个拷贝,再复制一份到子线程的栈上。这看起来确实有点多余,但本质是因为C标准允许这种实现方式——不同的编译器/标准库(比如GCC的libstdc、Clang的libc++)在处理线程函数对象时,为了适配底层的线程调度机制,会做这次额外的拷贝。当然,有些实现可能会优化掉这一步,但标准并没有禁止它。
怎么避免这次“多余”的拷贝?
如果你想减少拷贝次数,有两个常用办法:
用
std::move转移对象所有权:
给你的结构体A加上移动构造函数(推荐加上noexcept,让标准库更愿意用它):A(A &&other) noexcept { cout << "Move constructor this=" << this << " thread_id=" << this_thread::get_id() << endl; }然后构造线程时用
std::move:thread t(std::move(a));这样主线程里只会触发一次移动构造(代替拷贝),子线程里大概率不会再做额外拷贝(具体看标准库实现),效率更高。
用
std::ref传递引用:
如果你的函数对象不需要被线程拥有,而且能保证主线程里的原对象生命周期比线程长(比如main里的a在t.join()之后才销毁),可以用std::ref把对象包装成引用传递:thread t(std::ref(a));这样全程不会触发拷贝构造函数,但一定要注意原对象的生命周期——要是线程还在跑,原对象先没了,那就是妥妥的未定义行为!
内容的提问来源于stack exchange,提问作者Dongxu Wang
相关产品推荐
相关产品推荐

