有没有理由标准算法按价值取Lambda?

时间:2016-12-12 19:46:50

标签: c++ algorithm lambda pass-by-reference pass-by-value

所以我在这里问了一个问题:Lambda Works on Latest Visual Studio, but Doesn't Work Elsewhere我得到了响应,我的代码是实现定义的,因为标准的25.1 [algorithms.general] 10说:

  

除非另有说明,否则允许将函数对象作为参数的算法进行复制   那些功能对象是自由的对象身份很重要的程序员应该考虑使用a   指向非复制实现对象(如reference_wrapper<T>

)的包装类

我想了解发生这种情况的原因?我们被告知我们整个生命中通过引用来获取对象,为什么标准是按值获取函数对象,更糟糕的是我的链接问题制作了这些对象的副本?这样做是否有一些我不理解的优势?

2 个答案:

答案 0 :(得分:7)

std假设函数对象和迭代器可以自由复制。

std::ref提供了一种方法,可以将函数对象转换为具有兼容operator()的伪引用,该引用使用引用而不是值语义。所以没有什么大价值的东西会丢失。

如果你一生都被教导过参考物品,那就重新考虑一下。除非有充分的理由,否则按值获取对象。关于价值观的推理要容易得多;引用是指向程序中任何位置的任何状态的指针。

传统的引用用法,作为指向本地对象的指针,在使用它的上下文中没有被任何其他活动引用引用,它不是某人读代码而编译器也不能设想的。如果您以这种方式推理引用,它们不会为您的代码添加过多的复杂性。

但如果你以这种方式对他们进行推理,那么当你的假设受到侵犯时,你就会有错误,而且这些错误将是微妙的,严重的,意外的和可怕的。

一个典型的例子是operator=this的数量,并且参数引用同一个对象。但是任何带有两个相同类型的引用或指针的函数都有相同的问题。

但即使是一个引用也可能会破坏您的代码。我们来看看sort。在伪代码中:

void sort( Iterator start, Iterator end, Ordering order )

现在,让我们订购一个参考:

void sort( Iterator start, Iterator end, Ordering const& order )

这个怎么样?

std::function< void(int, int) > alice;
std::function< void(int, int) > bob;
alice = [&]( int x, int y ) { std:swap(alice, bob); return x<y; };
bob = [&]( int x, int y ) { std:swap(alice, bob); return x>y; };

现在,请致电sort( begin(vector), end(vector), alice )

每次调用<时,引用的order对象都会交换意义。现在这非常荒谬,但是当你Ordering const&时,优化器必须考虑到这种可能性,并在订购代码的每次调用时将其排除在外!

您不会执行上述操作(事实上,此特定实现是UB,因为它会违反std::sort上的任何合理要求);但编译器必须证明你每次跟随ordering或调用它时都没有做“喜欢那样”的事情(更改order中的代码)!这意味着不断重新加载状态order,或者内联并证明你做了非常疯狂的事情。

在采用按值时执行此操作会更加困难(并且基本上需要std::ref之类的内容)。优化器有一个函数对象,它是本地的,其状态是本地的。存储在其中的任何内容都是本地的,编译器和优化器知道谁可以合法地修改它。

你编写的每一个函数const&都离开了它的“本地范围”(比如称为C库函数),它不能假定const&的状态在它返回后保持不变。它必须从指针指向的任何位置重新加载数据。

现在,除非有充分的理由,否则我确实说过价值。而且有很多好的理由;你的类型移动或复制非常昂贵,例如,这是一个很好的理由。您正在向其写入数据。你实际上希望它在你每次阅读时都要改变。等

但默认行为应该是按值传递。如果你有充分的理由,只能转向参考,因为成本是分散的,很难确定。

答案 1 :(得分:0)

我不确定我是否有答案,但如果我的对象生命周期正确,我认为这是可移植的,安全的,并且增加零开销或复杂性:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>


// @pre f must be an r-value reference - i.e. a temporary
template<class F>
auto resist_copies(F &&f) {
    return std::reference_wrapper<F>(f);
};

void removeIntervals(std::vector<double> &values, const std::vector<std::pair<int, int>> &intervals) {
    values.resize(distance(
            begin(values),
            std::remove_if(begin(values), end(values),
                           resist_copies([i = 0U, it = cbegin(intervals), end = cend(intervals)](const auto&) mutable 
    {
        return it != end && ++i > it->first && (i <= it->second || (++it, true));
    }))));
}


int main(int argc, char **args) {
    // Intervals of indices I have to remove from values
    std::vector<std::pair<int, int>> intervals = {{1,  3},
                                                  {7,  9},
                                                  {13, 13}};

    // Vector of arbitrary values.
    std::vector<double> values = {4.2, 6.4, 2.3, 3.4, 9.1, 2.3, 0.6, 1.2, 0.3, 0.4, 6.4, 3.6, 1.4, 2.5, 7.5};
    removeIntervals(values, intervals);
    // intervals should contain 4.2,9.1,2.3,0.6,6.4,3.6,1.4,7.5

    std:
    copy(values.begin(), values.end(), std::ostream_iterator<double>(std::cout, ", "));
    std::cout << '\n';
}