向线程函数传递临时对象时,为何move constructor被调用两次?
我来帮你把这个逻辑掰明白——你遇到的两次移动构造调用,完全是std::thread构造函数的内部机制导致的,咱们一步步拆解:
第一步:参数先被移动到std::thread的内部存储
当你执行std::thread t(my_thread_func, Test());时,首先会创建一个临时的Test对象(这一步走默认构造,没输出)。紧接着,这个临时对象会被移动到std::thread自己的内部缓冲区里——这就是第一次触发移动构造的原因。
为啥要这么做?因为线程的执行上下文和当前主线程是分离的,std::thread必须把你传递的参数先存到自己管理的内存区域,确保线程启动时参数是可用的,不能依赖主线程的栈或者临时对象生命周期。
第二步:从内部存储移动到线程函数的参数
当线程真正开始执行my_thread_func的时候,std::thread会把刚才存在内部缓冲区里的Test对象,再次移动(通过std::move转换为右值)传递给线程函数的Test&&参数——这就是第二次移动构造调用。
这里要注意:线程内部存储的那个对象是个左值(它是thread对象里的一个实体),但你的线程函数需要的是右值引用,所以std::thread会用std::move把内部对象转换成右值,触发第二次移动。
额外补充:关于优化的可能性
其实现代编译器通常会对这种连续的移动操作做优化(比如移动省略),有可能把两次移动合并成一次。但因为你的移动构造函数里有std::cout输出语句,编译器可能认为这个构造函数有副作用,所以没做优化,你才会看到两次清晰的输出。你可以试试给移动构造函数加上noexcept,或者去掉输出语句,说不定就能看到优化后的结果。
最后再总结下:两次移动的核心原因就是std::thread的参数传递需要经过“暂存到内部缓冲区”和“转发给线程函数”这两个步骤,每一步都会触发一次移动(只要你的类型支持移动)。
内容的提问来源于stack exchange,提问作者PapaDiHatti

