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

使用函数对象构造线程时拷贝构造函数被调用两次的原因咨询

为什么用函数对象构造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++)在处理线程函数对象时,为了适配底层的线程调度机制,会做这次额外的拷贝。当然,有些实现可能会优化掉这一步,但标准并没有禁止它。

怎么避免这次“多余”的拷贝?

如果你想减少拷贝次数,有两个常用办法:

  1. 用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));
    

    这样主线程里只会触发一次移动构造(代替拷贝),子线程里大概率不会再做额外拷贝(具体看标准库实现),效率更高。

  2. 用std::ref传递引用:
    如果你的函数对象不需要被线程拥有,而且能保证主线程里的原对象生命周期比线程长(比如main里的a在t.join()之后才销毁),可以用std::ref把对象包装成引用传递:

    thread t(std::ref(a));
    

    这样全程不会触发拷贝构造函数,但一定要注意原对象的生命周期——要是线程还在跑,原对象先没了,那就是妥妥的未定义行为!

内容的提问来源于stack exchange,提问作者Dongxu Wang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:41:12