为什么序列运算算法谓词是通过复制传递的

Why the sequence-operation algorithms predicates are passed by copy?

本文关键字:复制 运算 算法 谓词 为什么      更新时间:2023-10-16

我想知道为什么函数是通过复制传递给algorithm函数的:

template <typename T> struct summatory
{
    summatory() : result(T()) {}
    void operator()(const T& value)
    { result += value; std::cout << value << "; ";};
    T result;
};
std::array<int, 10> a {{ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }};
summatory<int> sum;
std::cout << "nThe summation of: ";
std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;

我期待着以下输出:

求和:1;1.2.3.5.8.13;21;34;55;是:143

但是sum.result包含0,这是在ctor中指定的默认值。实现所需行为的唯一方法是捕获for_each:的返回值

sum = std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;

之所以会发生这种情况,是因为函子是通过复制而不是通过引用传递给for_each的:

template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );

因此,外部函子保持不变,而内部函子(它是外部函子的副本)会更新,并在执行算法(实时演示)后返回,因此在完成所有操作后,结果会再次被复制(或移动)


这样做肯定有充分的理由,但我并没有真正意识到这个设计的基本原理,所以我的问题是:

  • 为什么序列运算算法的谓词是通过复制而不是引用传递的
  • 在通过引用的方法之前,通过复制的方法有什么优势

这主要是由于历史原因。在98年,当整个算法被纳入标准参考文献时,出现了各种各样的问题。这最终通过C++03及更高版本的核心和库DR得到了解决。此外,合理的ref包装器和实际工作的绑定仅在TR1中才到达。

那些在早期的C++98中尝试使用具有使用ref params或return的函数的算法的人可以回忆起各种各样的麻烦。自写算法也容易遇到可怕的"引用到引用"问题。

传递价值至少效果很好,几乎没有造成太多问题——boost在早期就有ref和cref来帮助你解决需要调整的地方。

这纯粹是猜测,但。。。

让我们暂时假设它通过引用const来实现。这意味着您的所有成员都必须是可变的,并且运算符必须是const。这感觉不太"对"。

让我们暂时假设它通过引用非常数来实现。它会调用一个非常数运算符,成员可以很好地处理。但是,如果你想传递一个特别的对象呢?就像绑定操作的结果一样(即使C++98也有丑陋而简单的绑定工具)?或者类型本身只做你需要的一切,然后你就不需要对象了,只想像for_each(b,e,my_functor());一样调用它?这是行不通的,因为临时变量不能绑定到非常量引用。

因此,也许不是最好的,但最不坏的选择是按值取值,在过程中尽可能多地复制它(希望不要太频繁),然后在完成后,从for_each返回它。这可以很好地处理总结对象的较低复杂性,不需要添加诸如引用const方法之类的可变内容,也可以处理临时对象。

但是YMMV,委员会成员也很可能如此,我想最终是对他们认为最有可能适合大多数用例的内容进行了投票。

也许这是一个变通方法。捕获函子作为引用,并在lambda 中调用它

std::for_each(a.begin(), a.end(), [&sum] (T& value) 
    {
        sum(value);   
    });
std::cout << "is: " << sum.result;