了解STL中的函数

时间:2010-06-26 02:52:51

标签: c++ stl functor function-object

引自N M Jousttis的“The C ++ Standard Library”,第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的每个元素调用“()”。同样,这是对operator()的调用 传递的AddValue类型的临时函数对象。实际元素作为一个传递 论点。函数对象将其值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

含义是为每个调用创建的新仿函数,还是每个调用使用的仿函数?

6 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:1)

[编辑]我最初误解了作者的原意。我纠正了这个错误。

  

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

     

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

     

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 ++中使用函数式编程,但是很少。当你经历了很多麻烦并积累时间来制作两到三行代码时,你真的要问自己并深入思考它是否值得,而不仅仅是为了你自己,而是为了每个工作的人用你的代码。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

我认为像这样复制仿函数的原因是为了使for_each更加通用。

想象一下通过引用获取仿函数的实现。如果仿函数是右值(例如,如果它是从另一个函数返回的话),则会破坏:

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

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

唯一真正通用的解决方案是按值传递仿函数。然后它适用于const,非const,rvalue和左值仿函数。

答案 5 :(得分:0)

您的AddValue构造函数需要int,因此当您从*coll.begin()构造它时,您的集合的第一个成员的值将用于初始化成员变量theValue

现在修复此问题(没有其他内容修改theValue),因此每次theValue对象使用此AddValue对象或此AddValue对象的任何副本时,它仍将具有与其初始化的值相同。

这是构建第一个*coll.begin()对象时AddValue 的值,而不是*coll.begin()可能已修改为的值。< / p>