std::bind与std::thread如何复制完美转发的参数?实现机制解析
核心原因:参数的"存储-延迟调用"特性
std::bind和std::thread的本质不是立即调用目标函数,而是先把函数和参数存储起来,等到后续时机(bind的返回对象被调用、线程开始执行)才执行目标函数。完美转发在这里的作用是把传入的参数高效地转发到存储环节,而不是直接转发给目标函数——复制操作就发生在这个存储过程中。
复制发生的具体位置
以std::thread为例,它的构造函数会接收参数并将其存储到线程内部的独立存储空间(避免原参数在主线程提前销毁)。简化模拟其内部逻辑如下:
#include <tuple> #include <functional> template<typename Func, typename... Args> class SimulatedThread { private: Func stored_func; // 用std::decay_t去除引用、cv限定,存储参数的副本/移动后的对象 std::tuple<std::decay_t<Args>...> stored_args; public: // 构造函数通过完美转发接收参数 template<typename F, typename... A> SimulatedThread(F&& func, A&&... args) : stored_func(std::forward<F>(func)), // 把参数转发到tuple的构造:左值触发复制,右值触发移动 stored_args(std::forward<A>(args)...) {} // 后续执行时,将存储的参数转发给目标函数 void execute() { std::apply(stored_func, stored_args); } };
这里的关键是std::decay_t<Args>:它会把传入的左值引用T&、右值引用T&&都衰减为值类型T,因此存储的是参数的副本(如果传入左值)或移动后的对象(如果传入右值)。这就是你看到的复制操作的来源。
std::ref的作用:传递引用而非副本
当你需要让目标函数直接操作原对象(而不是副本)时,std::ref会把左值包装成std::reference_wrapper<T>对象。这个包装类型的std::decay_t结果仍是std::reference_wrapper<T>,而非值类型T。
当后续调用目标函数时,std::reference_wrapper<T>会隐式转换为T&,从而让目标函数拿到原对象的引用。例如:
void func(int& x) { x += 1; } int main() { int r = 9; // 用std::ref包装r,存储的是std::reference_wrapper<int> SimulatedThread t(func, std::ref(r)); t.execute(); // r的值变为10,而非操作副本 return 0; }
如果不用std::ref,存储的就是int类型的副本,func操作的是副本,原r的值不会改变。
对比你的示例代码
你提供的示例是直接将参数转发给目标函数,没有中间的存储环节,因此不会产生副本:
template <typename T> void fct(T&& x) { f(std::forward<T>(x)); // 注意:std::forward需要显式指定模板参数 }
这里的完美转发是直接把参数传递给f,没有存储步骤,所以不会复制。而std::bind/std::thread的差异就在于多了"存储参数"这一步,这才是复制的根源。
内容的提问来源于stack exchange,提问作者Chenoille

