C++中右值直接传参产生拷贝是标准规定还是编译器行为?原因是什么?
结论
你观察到的行为是C++语言规范明确规定的,不属于编译器自定义行为;拷贝没有被优化消除的核心原因是std::function的类型擦除中间层阻断了复制消除(copy elision)的适用条件。
原理说明
- C++17版本起明确规定了强制复制消除的适用场景:只有当纯右值(prvalue,比如你代码里的
Bar{})直接用于初始化同类型对象时,编译器才必须跳过拷贝/移动构造逻辑,直接在目标对象的内存地址完成构造。 - 复制消除的前提是编译期就能确定「源临时对象和目标对象的内存地址可以直接重合」,无法跨间接调用层生效。
你代码中使用std::function<void(Bar)>存储lambda,std::function的实现依赖类型擦除技术:当你调用setter(Bar{})时,参数首先会被传给std::function的operator(),先在类型擦除的中间层构造一份参数实例,再转发给内部存储的lambda。编译器无法跨这个间接调用层,把你外层构造的Bar{}直接构造到lambda按值参数a的内存地址上,因此必须产生一次额外的移动/拷贝,这完全符合C++标准的要求。
测试结果验证
我们可以对应你给出的输出验证上述逻辑,以in-place then copy场景为例:
- 构造
Bar{}临时纯右值传入setter std::function的中间层移动构造一份Bar对象转发给lambda参数a,mv_count +1- lambda内执行
bar = a触发拷贝赋值,cp_count +1
最终结果为mv_count=1 cp_count=1,和你的测试输出完全匹配。
如果你把std::function替换为直接调用lambda,就会发现复制消除正常生效,不会产生额外的移动/拷贝开销。
内容的提问来源于stack exchange,提问作者TigerChan
相关产品推荐
相关产品推荐

