在下面的代码中,lambda捕获列表中的std::move
对我来说是不必要的,但编译器似乎确实需要它。
由于存在用于复制shared_ptr的额外代码,如果我不使用std::move
,则会生成该代码。
问题是,为什么编译器不能自己优化它。
template<typename T>
std::function<void(void)> prepLambdaImpl(std::shared_ptr<T> aptr) {
#ifdef CONVERT_SHARED_PTR_TO_XVALUE
return [aptr=std::move(aptr)]
#else
return [aptr]
#endif
{
printf("use count: %ldn", aptr.use_count());
};
}
工作示例:https://godbolt.org/z/W3oWEjsjK
如果没有std::move
,std::shared_ptr
将被复制到lambda中。复制(但不移动(std::shared_ptr
需要递增其原子引用计数(并在副本销毁后再次递减(。
理论上,编译器可以从副本中删除原子增量,从aptr
参数的析构函数中删除原子减量,因为它们会取消,假设它确保没有其他线程可以观察到中间状态,但编译器往往是保守的,不会优化原子操作,因为这通常不是用户想要的。
自动移动只在某些特定情况下发生在return语句中,这里不满足这些情况。
上次使用不会触发移动而不是复制。
假设规则允许优化,只要可观察的行为不变。虽然我们知道这两种形式是等价的,但要知道并不容易。尤其是移动和复制是不相关的功能;因此优化器只使用复制代码来尝试优化它(它可能会错过一些额外的信息作为类的不变量(因为函数开始时refcounter不是0((。