You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何ranges::accumulate调用invoke时不传递std::move(init)?

解析accumulate.hpp中传递范围时init被std::move的原因

最近看到截至2018年3月17日accumulate.hpp的d5e9afc提交里的这段代码,发现传递范围时init会被std::move一次,咱们来拆解下背后的逻辑:

首先看范围版本的重载代码:

T operator()(Rng && rng, T init, Op op = Op{}, P proj = P{}) const {
    return (*this)(begin(rng), end(rng), std::move(init), std::move(op), std::move(proj));
}

这段代码会调用迭代器版本的重载:

T operator()(I begin, S end, T init, Op op = Op{}, P proj = P{}) const {
    for(; begin != end; ++begin)
        init = invoke(op, init, invoke(proj, *begin));
    return init;
}

为啥第一个重载里要对init用std::move?

核心目的是避免不必要的拷贝开销。当你调用范围版本的接口时,传入的init可能是临时对象,或者是你不再需要的左值对象——用std::move把它转换成右值引用后,迭代器版本的重载在接收init参数时,如果T类型支持移动构造,就会优先使用移动构造来初始化参数,而不是拷贝构造。这对std::vector、std::string这类拷贝成本极高的类型来说,能带来很明显的性能提升。

那第二个重载的循环里为啥不用std::move(init)?

原因很简单:循环里的init是一个有名字的左值对象,我们需要持续对它进行累加操作。如果这里把init用std::move转换成右值传递给invoke(op, init, ...),要么会匹配到op的右值重载(如果有的话),导致init的资源被转移走,后续循环就会操作一个空的对象;要么会破坏累加的语义——毕竟我们是要把新值合并到init里,而不是把init的资源移走再重新赋值。

另外补充一点:第二个重载最后返回init时,其实可以考虑用std::move(init),因为函数参数不属于局部对象,RVO(返回值优化)不一定能生效,用std::move可以触发移动构造返回值。不过原代码没这么写,可能是出于兼容性考虑,或者觉得收益有限。

内容的提问来源于stack exchange,提问作者sandthorn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:23:42