C++线程函数按值接收类对象:为何调用移动构造函数?
问题背景与代码示例
#include <iostream> #include <thread> template<int Num> class MyClass { public: MyClass(int val) : val_(val) {} // Copy constructor MyClass(const MyClass& other) : val_(other.val_) { std::cout << "Copy constructor called" << std::endl; } // Move constructor MyClass(MyClass&& other) noexcept : val_(other.val_) { std::cout << "Move constructor called" << std::endl; } private: int val_; }; template<int Num> void threadFunction(MyClass<Num> myObj) { std::cout << "Inside thread" << std::endl; } int main() { MyClass<1> obj(42); std::thread t1(threadFunction<1>, obj); // <-- 调用复制+移动构造 std::thread t2(threadFunction<1>, std::ref(obj)); // <-- 仅调用复制构造 t1.join(); t2.join(); return 0; }
问题
已知此示例中std::ref(obj)并非必需,但不同传参方式会触发不同的构造函数:直接传递obj时会调用复制构造函数和移动构造函数,传递std::ref(obj)时仅调用复制构造函数。请问这一差异的原因是什么?
原因解析
核心差异来自std::thread的参数传递机制,以及std::ref带来的类型包装逻辑:
std::thread的底层设计逻辑:创建线程时,std::thread会先把所有传入的参数复制到线程自身的内部存储区(独立于主线程栈空间),再将这些副本传递给线程函数。这么做是为了避免原对象被提前销毁,导致线程访问悬空引用。
1. 直接传递obj的情况
当直接传入obj(类型为MyClass<1>):
- 第一步:
std::thread会对obj执行复制构造,生成一个存储在线程内部的临时副本(这是第一次调用复制构造函数)。 - 第二步:线程函数
threadFunction的参数是值传递(MyClass<Num> myObj),此时线程内部的临时副本是右值,会触发移动构造,把临时对象的资源转移给myObj。 - 最终表现为:复制构造 + 移动构造各调用一次。
2. 传递std::ref(obj)的情况
std::ref(obj)返回std::reference_wrapper<MyClass<1>>类型的对象,它是对obj的轻量引用包装:
- 第一步:
std::thread复制的是这个reference_wrapper对象(该复制操作不会触发MyClass的任何构造函数)。 - 第二步:线程函数期望接收
MyClass<Num>类型参数,因此会将reference_wrapper解引用,得到原对象obj的左值引用,再基于这个左值执行复制构造生成myObj。 - 最终表现为:仅调用一次复制构造函数。
内容的提问来源于stack exchange,提问作者Mathieu
相关产品推荐
相关产品推荐

