2

引自 NM Jousttis 的“C++ 标准库”,第 5.9 节


#include < iostream>
#include < list>
#include < algorithm>

using namespace std;

//function object that adds the value with which it is initialized
class AddValue {
    private:
       int the Value; //the value to add
    public:
       //constructor initializes the value to add
       AddValue(int v) : theValue(v) {    }
       //the "function call" for the element adds the value
       void operator() (int& elem) const {  elem += theValue; }
 };

int main()
{
      list<int> coll;
      for (int i=1; i<=9; ++i) 
         coll.push_back(i); 

      //The first call of for_each() adds 10 to each value:
      for_each (coll.begin(), coll.end(), AddValue(10)) ; 

在这里,表达式 AddValue(10) 创建了一个 AddValue 类型的对象,该对象使用值 10 进行初始化。AddValue 的构造函数将该值存储为成员 theValue。在 for_each() 内部,为 coll 的每个元素调用“()”。同样,这是对传递的 AddValue 类型的临时函数对象的 operator () 调用。实际元素作为参数传递。函数对象将其值 10 添加到每个元素。然后元素具有以下值: 加 10 后:

11 12 13 14 15 16 17 18 19

for_each() 的第二次调用使用相同的功能将第一个元素的值添加到每个元素。它使用集合的第一个元素初始化 AddValue 类型的临时函数对象:

for_each (coll.begin(), coll.end(), AddValue (*coll. begin()) ) ; 

添加第一个元素后,输出如下:

22 23 24 25 26 27 28 29 30

我不明白的是在第二种情况下为什么输出不是

22 34 35 36 37 38 39 40 41

意思是为每次调用创建一个新的仿函数,还是每次调用都使用仿函数?

4

6 回答 6

6

该表达式AddValue(*coll. begin())创建一个临时对象 class AddValue。然后将该临时值传递给for_each函数。for_each然后调用对象的函数调用运算符 - 即- 为从tooperator()的每个元素调用一次。coll.begin()coll.end()

从技术上讲,for_each按值(而不是引用)获取函子参数,因此它实际上是在临时的副本上操作,而不是临时本身。

于 2010-06-26T03:06:56.810 回答
1

[编辑] 原来误解了作者的初衷。我纠正了错误。

for_each (coll.begin(), coll.end(), AddValue (*coll.begin()) ) ;

添加第一个元素后,输出如下:

22 23 24 25 26 27 28 29 30

我不明白的是在第二种情况下为什么输出不是

22 34 35 36 37 38 39 40 41

意思是为每次调用创建一个新的仿函数,还是每次调用都使用仿函数?

首先,不管for_each算法是否复制你传入的函子都是无关紧要的。相关的是您应该存储指向第一个元素而不是指针的迭代器或指针。如果你这样做并且每次都取消引用它,它应该可以解决问题。

struct AddValue 
{
    const int* ptr;
    explicit AddValue(const int* iptr): ptr(iptr) {}
    void operator() (int& elem) const {elem += *ptr; }
};

int main()
{
    vector<int> v;
    for (int j=1; j <= 9; ++j)
        v.push_back(j + 10);
    for_each(v.begin(), v.end(), AddValue(&v[0]) );
    // v will be [22 34 35 36 37 38 39 40 41]
}

但是,我将不得不在函数式编程方面投入两分钱。您可以通过函数式编程获得非常简洁、巧妙紧凑的代码。但是,调试起来也是一个很大的痛苦,并且具有去中心化代码的效果。除非您的代码可以从中显着受益,否则请尽可能考虑基于迭代器的简单 for 循环,因为它可以让您的程序员同事更轻松地调试和阅读您的代码。

过去我犯了对函数式编程过于严厉的错误,这让我的团队讨厌我。我痴迷于使用组合谓词逻辑编写极其紧凑的代码,并构建了可以反复和组合重用的庞大的函数对象库。实际上,我所做的只是花了很多时间编写函子,而我本来可以编写简单、同样可重用的函数,而不是可以从简单的基于迭代器的 for 循环中调用(并且内联同样容易)(甚至更容易编写) C++0x 的基于范围的 for 循环和 BOOST_FOR_EACH)。我仍然在 C++ 中使用函数式编程,但很少使用。当你经历了很多麻烦并加紧构建时间以将两三行代码合二为一时,你真的必须问问自己并深入思考是否它'

于 2010-06-27T15:49:05.200 回答
0

是的,就是你说的。函子默认按值传递,因此它们被复制到 std::for_each 的代码中。但是,您可以编写自己的 std::for_each 版本,明确声明您希望通过引用传递函子。

于 2010-06-26T02:58:19.403 回答
0

是的。一个新的仿函数副本通过 for_each 传递。你正在阅读的这本书解释了这一点。

于 2010-06-26T02:58:43.443 回答
0

我相信函子被这样复制的原因是为了for_each更通用。

想象一个通过引用获取函子的实现。如果函子是右值(例如,如果它是从另一个函数返回的),那将会中断:

std::for_each(first, last, get_functor(...))

当然,在这些情况下,算法可以通过 const 引用获取函子(因为它们可以绑定到右值),但是函子必须是 const。

唯一真正通用的解决方案是按值传递函子。然后它与 const、non-const、rvalue 和 lvalue 函子一起工作。

于 2010-06-26T10:08:14.160 回答
0

您的AddValue构造函数采用intso 当您从*coll.begin()集合的第一个成员的值构造它时用于初始化成员变量theValue

这现在是固定的(没有其他修改theValue),所以每次theValue从这个AddValue对象或这个对象的任何副本中使用AddValue它仍然具有与它初始化时相同的值。

这是构造第一个对象时的值,而不是可能已修改*coll.begin() 的值。AddValue*coll.begin()

于 2010-06-26T10:20:15.467 回答