std::bind绑定类对象时多次调用拷贝构造函数的原因及相关疑问
std::bind行为疑问解答
各版本现象解释
版本1
你猜测的原因是正确的,两次拷贝的触发点分别是:
std::bind调用时,会对传入的左值n做一次拷贝,将拷贝后的NAME实例存储在自己返回的可调用对象中,这是第一次拷贝。- 将
bind返回的可调用对象赋值给std::function<void()>时,std::function的构造函数会对传入的可调用对象做一次拷贝存储,对应内部的NAME实例也会被拷贝一次,这是第二次拷贝。
版本2
你使用的在线编译器搭载的是旧版本标准库,std::bind的返回类型未实现移动构造函数,额外多了一次拷贝:
- 首先
std::bind内部拷贝n,第一次拷贝。 - 你对
bind返回的临时对象显式调用std::move,反而阻止了编译器的返回值优化,由于bind返回类型没有移动构造,只能拷贝这个临时对象作为std::function构造的入参,对应NAME实例第二次拷贝。 std::function存储可调用对象时,第三次拷贝内部的NAME实例。
版本3
用auto直接接收bind的返回值,不需要经过std::function的类型擦除和存储拷贝:
- 编译器会触发返回值优化(RVO),直接将
bind内部构造的可调用对象构造到callable的内存空间中,不需要额外拷贝。 - 最终只有
bind调用时拷贝n的1次拷贝操作。
版本4
显式std::move阻止了返回值优化:
std::bind内部拷贝n,第一次拷贝。- 由于
bind返回类型没有移动构造,移动构造callable时实际触发拷贝操作,内部NAME实例第二次拷贝。
补充疑问解答
问题1
两者类型完全不同:
auto推导的callable_1类型是标准未明确定义的std::bind返回类型,属于编译器生成的未命名可调用类类型,和lambda的未命名类性质类似,不同编译器的实现存在差异。callable_2的类型是明确的std::function<void()>,它是类型擦除的可调用对象包装器,会独立存储一份传入的可调用对象副本。
问题2
你示例中的两种写法都是完全合法的:std::bind绑定非静态成员函数时,第二个参数只要能作为成员函数的调用对象即可:
- 传值
n时,bind会拷贝一份n存储在返回对象中,调用f()时对拷贝的实例调用成员函数,没有合法性问题。 - 替换为
std::ref(n)也合法,此时bind会存储std::reference_wrapper<NAME>指向原n,调用时对原n操作,只要调用时原n未销毁就可以正常运行。
问题3
二者功能并不完全等价,除了编码风格外差异较大:
- 你示例中的
std::bind(&NAME::f, cref(n))实际会编译失败:因为NAME::f是非const成员函数,不能对const引用的实例调用,lambda写法[&n]{n.f()}则没有这个问题,你如果要bind等效的话需要用std::ref而非std::cref。 - lambda的可读性远高于bind,尤其是绑定逻辑复杂的场景下,lambda的逻辑更直观易懂。
- 编译器对lambda的优化难度更低,性能通常比bind更好。
- bind支持参数的灵活重排、占位符绑定等特殊用法,lambda要实现相同逻辑需要额外写代码封装。
问题4
假设你已经正确引入了std::placeholders::_1占位符,两条语句都是合法的:
callable(n):调用时会对传入的n做一次拷贝,对拷贝的实例调用f()。callable(std::ref(n)):调用时std::reference_wrapper<NAME>会自动解引用为NAME&,直接对原n调用f()。
内容的提问来源于stack exchange,提问作者Anoop Rana
相关产品推荐
相关产品推荐

