在标准算法中,哪里通过引用捕获更正确?

时间:2017-04-12 06:25:08

标签: c++ algorithm lambda

CppCoreGuideline F.52表示通过引用捕获算法中使用的lambda更为正确。

我不明白为什么 - 算法主要是用值语义定义的。

在什么情况下 通过引用捕获更正确?

4 个答案:

答案 0 :(得分:4)

请注意,指南并未说明"正确性,"它说"效率和正确性。"通过引用捕获它当然更有效,因为标准算法中使用的仿函数和谓词是按值传递的。如果您需要访问其中的大(gish)本地对象,按值捕获将意味着使用仿函数的每个副本复制它们。通过引用捕获可以直接处理局部变量。

我承认我实际上无法想到使用引用有助于正确性的情况。原因很简单:按值捕获的实体是const - 默认情况下是合格的,所以如果你打算修改lambda中的局部变量并意外地通过复制而不是引用来捕获它,你会得到一个编译错误(除非你标记lambda的调用操作符mutable,此时你和&# #39;显然需要给予足够的重视而不需要经验法则。

答案 1 :(得分:2)

首先,并不总是可以按值捕获。示例中的对象,例如包含threads.and因此很可能不会复制可构造的。

另一个例子是循环中的随机数生成器:通常你想确保你不会一遍又一遍地得到相同的序列,如果你按值捕获就会发生这种情况(正如angw指出的那样,你的lambda必须是可变的才能在第一时间工作。)

答案 2 :(得分:1)

指南指出:

  

F.52:首选在本地使用的lambdas中通过引用捕获,包括传递给算法

     

为了提高效率和正确性,您几乎总是希望在本地使用lambda时通过引用捕获。这包括在编写或调用本地并行算法时,因为它们在返回之前会加入。

关于通过引用捕获效率的问题可以确保您不会复制大型对象并浪费宝贵的资源(因为lambda本身也可以被复制)。另外,如果您的对象不可复制,有时它是唯一可行的方法。

关于正确性我倾向于同意其他答案,无论如何要注意这个事实

  

lambdas [..]将在本地使用

(这样我们就无法处理悬空引用),人们可能会推测,在某些极端情况下,按价值捕获可能(可以说)行为不直观:

int the_variable = 42;

void test( int& value ) {
    auto modify_the_variable = [value] () mutable {
        value = 2; // Not actually a reference this one
    };
    modify_the_variable();
}

int main()
{
    test(the_variable);
    std::cout << the_variable; // Still 42
}

有人可能会认为,由于按值捕获有效且lambda标记为mutable,因此捕获的value的类型将为int&。无论如何§5.1.5/ 16说不然

  

如果

,则通过副本捕获实体      

(16.1) - 隐式捕获,捕获默认值为=,捕获的实体不是* this,或者   (16.2) - 使用不属于此形式的捕获显式捕获它,&amp;标识符,或&amp;识别码   初始化程序。

     

对于通过复制捕获的每个实体,在闭包类型中声明一个未命名的非静态数据成员。该   这些成员的声明顺序未指定。 此类数据成员的类型是引用的类型   如果实体是对象的引用,则对实体引用的函数类型的左值引用   是对函数的引用,或者是对应的捕获实体的类型,否则

(强调我的)

在这种情况下,通过引用捕获将做正确的事情。请注意,指南说:

  

包括传递给算法

即。不仅限于标准库算法。

答案 3 :(得分:0)

是什么让你认为“算法主要用值语义定义”?

如果您查找需要存储内部值的算法,例如std::findstd::fillstd::count等 - 它们都通过const引用捕获它们的输入。

我同意,lambdas一般可以在它们定义的范围之外使用,正如你在github问题中提到的那样可能导致悬挂引用 - 这就是为什么指南专门提到算法中使用的lambda。

可以肯定地说,通过引用捕获本地对象将使算法能够使用该对象几乎没有开销,当然也没有不必要的副本。

“corectness”参数显然是指lambdas改变捕获的值。在这种情况下按值捕获很容易被忽略,因为它仍然可以编译。该指南的主要内容是“不要试图决定这种具体情况需要什么样的捕获,而只是总是通过引用来捕获”。

重要的是要注意所有推理都不适用于迭代器和仿函数对象 - 它们不是lambda闭包的一部分。看一下std::find声明:

template< class InputIt, class T >
InputIt find( InputIt first, InputIt last, const T& value );  

const T& value在这里很重要 - 如果你用find_if实现了相同的逻辑,那就是你的lambda会捕获的内容。
  迭代器只是循环机制的一部分,它们通过值传递的事实与所讨论的指南完全无关。