【问题标题】:Cost of capture by reference/value in lambda function?通过 lambda 函数中的引用/值捕获的成本?
【发布时间】:2026-01-19 13:20:05
【问题描述】:

考虑以下代码:

#include <iostream>
#include <algorithm>
#include <numeric>

int main()
{
    const unsigned int size = 1000;
    std::vector<int> v(size);
    unsigned int cst = size/2;
    std::iota(v.begin(), v.end(), 0);
    std::random_shuffle(v.begin(), v.end());
    std::cout<<std::find_if(v.begin(), v.end(), [&cst](const int& i){return i == cst;})-v.begin()<<std::endl;
    std::cout<<std::find_if(v.begin(), v.end(), [=](const int& i){return i == cst;})-v.begin()<<std::endl;
    return 0;
}

这段代码用值填充一个向量,对其进行洗牌,然后搜索指定值的索引(这只是一个例子来说明我的问题)。此值cst 可以通过引用或通过 lambda 函数中的值来捕获。

我的问题:两个版本之间的性能是否存在差异,或者编译器是否会以相同的方式对其进行优化?

通过值传递常量基本类型和通过引用传递常量类是一个好规则吗(就像在普通函数中一样)?

【问题讨论】:

  • 我知道你已经接受了答案,但要考虑的一件有趣的事情是按值捕获是默认 const,这意味着它可以很容易地从复制整个 obj 中优化出来,因为它不会被更改

标签: c++ optimization c++11 lambda pass-by-reference


【解决方案1】:

cstunsigned int,因此不太可能有所作为。但是,如果您对其中包含大量数据的大型类执行此操作,则可能会有所不同,通过引用传递会更快。

在这种情况下要考虑的另一件事是,在迭代向量时,对象只被复制一次。如果你看一下 STL 函数,大多数东西都是通过 const 引用或普通引用传递的,我不明白为什么捕获变量应该有任何不同。尽管不幸的是,您无法将变量捕获为 const。

当然,在通过引用传递时你总是要小心,因为你可以修改它,我认为在这种情况下,最好只作为 const 引用传递。

最后要考虑的一件事是,由于编译器可能能够优化差异,您可能应该只使用您认为最能说明您的意图的形式。所以基本上我同意你的假设,你应该

通过值传递常量基本类型,通过引用传递常量类

【讨论】:

    【解决方案2】:

    lambda 捕获并不真正相关。区别在于:

    int x = y;
    
    for (...)
        if (x == z)
           ...
    

    const int& x = y;
    
    for (...)
        if (x == z)
           ...
    

    也就是说,存储对 const int 的引用与获取 int 的副本。第一个版本永远不会变慢,但我认为优化器会设法为两者生成相同的代码。编译这两个版本,然后反汇编看看会发生什么。

    【讨论】:

    • 这家伙文森特问了很多问题
    【解决方案3】:

    在实践中,小类型没有性能差异。

    clang -O3 在这两种情况下我都会得到相同的代码。如果没有优化clang 会生成不同的代码,并且复制的版本恰好小了一条指令。

    $ clang ref.cpp -O3 -std=c++11 -S -o ref.s
    $ clang cpy.cpp -O3 -std=c++11 -S -o cpy.s
    $ diff ref.s cpy.s
    

    与 const 相关的差异很小。

    复制捕获为您提供const unsigned 值。这将不会编译:

    unsigned cst = 123;
    [=](const int& i){ return i == ++cst; }
    

    非常量变量的引用捕获导致非常量unsigned&amp; 引用。这会修改原始值作为副作用:

    unsigned cst = 123;
    [&](const int& i){ return i == ++cst; }
    

    作为一个良好的规则应该避免复制大对象。如果小对象在 lambda 的范围内应该是常量,但在当前范围内不是常量,则复制​​捕获是一个不错的选择。如果 lambda 的生命周期超过本地对象的生命周期,则复制捕获是唯一的选择。

    【讨论】:

    • "复制捕获给你一个 const 无符号值。"除非是mutable[=](const int&amp; i) mutable { return i == ++cst; }
    • @newacct 很好,我没有意识到这一点。此外,clang 似乎只使全局/文件静态/本地捕获的变量为 const,而不是函数静态/类静态/成员变量。
    • 明显的非常量副本捕获实际上是捕获 [this] 而不是单个函数静态/类静态/成员变量。
    最近更新 更多