使用std::bind传递可移动类型参数的问题及疑问
嘿,你遇到的问题核心其实不在std::bind本身,而是**std::function对其内部存储的可调用对象有拷贝构造的硬性要求**。咱们一步步拆解清楚:
1. 调用链里的拷贝卡点
当你执行f.Do(std::bind(...))时,整个流程是这样的:
std::bind会返回一个匿名的可调用对象(咱们姑且叫它bind_obj)。这个对象的拷贝能力完全绑定它内部存储的参数——因为你绑定了一个不可拷贝的Param,所以bind_obj也会变成不可拷贝的(拷贝bind_obj就需要拷贝它存的Param,但你没给Param写拷贝构造)。- 而
Foo::Do的参数是const Callback&(也就是const std::function<void(int)>&),这意味着要从bind_obj构造一个临时的std::function对象。但std::function的设计逻辑是:它本身是可拷贝的,所以内部存储的可调用对象也必须支持拷贝构造——构造std::function时,它会把传入的可调用对象拷贝一份存起来。
这就是为什么去掉Param的拷贝构造后代码直接挂掉:std::function没法构造一个不可拷贝的bind_obj的拷贝。
2. 显式指定bind模板参数为啥没用?
你尝试显式写std::bind<decltype(&Bar::Callback2), Bar*, Param>(...),确实让bind内部存了一个Param实例(而不是引用),但这并没有改变bind_obj的可拷贝性——只要Param不可拷贝,bind_obj就还是不可拷贝,std::function照样没法接受它。
可行的解决方案
如果你不想给Param加拷贝构造,有几个路子可以走:
方案1:让Foo::Do接受右值引用的std::function
把Foo::Do的参数改成右值引用,这样就能直接把bind_obj移动到std::function里,完全跳过拷贝:
struct Foo { using Callback = std::function<void(int)>; void Do(Callback &&callback) { // 改成右值引用 callback(5); } };
这样一来,传递std::bind的结果时,会触发std::function的移动构造,而bind_obj是支持移动的(因为Param有移动构造),代码就能正常跑了。
方案2:用std::shared_ptr包装Param
如果不能改Foo::Do的签名,那就把Param包进std::shared_ptr里。shared_ptr是可拷贝的,这样bind_obj也会变成可拷贝,完美满足std::function的要求:
struct Bar { Bar() { Foo f; f.Do(std::bind(&Bar::Callback1, this, std::placeholders::_1)); auto p = std::make_shared<Param>(Param::Create()); f.Do(std::bind(&Bar::Callback2, this, p, std::placeholders::_1)); } void Callback1(int a) { printf("%d\n", a); } void Callback2(const std::shared_ptr<Param> &p, int a) { printf("%d %d\n", a, p->value_); } };
这里shared_ptr的拷贝只是复制指针,不会真的拷贝Param实例,完全符合你只想移动Param的需求。
方案3:lambda+移动捕获(搭配方案1使用)
你也可以用lambda替代std::bind,但要注意:如果lambda捕获了一个不可拷贝的Param,lambda本身的拷贝构造会被禁用,所以还是得搭配方案1(让Do接受右值引用)才能用:
struct Bar { Bar() { Foo f; f.Do([this](int a) { Callback1(a); }); Param p(Param::Create()); // 移动捕获p到lambda里 f.Do([this, p = std::move(p)](int a) { Callback2(p, a); }); } // 其他代码不变 };
这个写法更灵活,也避免了std::bind的一些模板推导坑。
内容的提问来源于stack exchange,提问作者Rudolfs Bundulis

