You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 06:53:42