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.resultcontains 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 );

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


必须有充分的理由以这种方式进行工作,但我并没有真正意识到这种设计的基本原理,所以我的问题是:

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

3 回答 3

7

这主要是出于历史原因。在 98 年,当整个算法成为标准参考时,出现了各种各样的问题。这最终通过 C++03 及更高版本的核心和库 DR 得到了解决。同样明智的 ref-wrappers 和实际工作的绑定仅在 TR1 中出现。

那些尝试在早期的 C++98 中使用具有使用 ref 参数或返回函数的算法的人可以回忆起各种麻烦。自行编写的算法也容易遇到可怕的“参考参考”问题。

按值传递至少可以正常工作,并且几乎不会产生很多问题——boost 很早就有 ref 和 cref 来帮助你在需要调整的地方。

于 2013-06-21T11:57:27.937 回答
5

这纯粹是猜测,但...

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

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

所以也许不是最好的,但最不坏的选择是按价值取值,在过程中尽可能多地复制它(希望不要太频繁),然后在完成后从 for_each 中返回它。这适用于您的汇总对象的相当低的复杂性,不需要添加可变的东西,例如引用到常量的方法,并且也适用于临时对象。

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

于 2013-06-21T12:26:37.153 回答
1

Maybe this could be a workaround. Capture the functor as reference and call it in a lambda

std::for_each(a.begin(), a.end(), [&sum] (T& value) 
    {
        sum(value);   
    });

std::cout << "is: " << sum.result;
于 2013-06-21T12:03:14.667 回答